Add email to users
authorChocobozzz <florian.bigard@gmail.com>
Sat, 18 Feb 2017 08:29:59 +0000 (09:29 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Sat, 18 Feb 2017 08:29:59 +0000 (09:29 +0100)
19 files changed:
client/src/app/+admin/users/shared/user.service.ts
client/src/app/+admin/users/user-add/user-add.component.html
client/src/app/+admin/users/user-add/user-add.component.ts
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/app.component.html
client/src/app/shared/forms/form-validators/email.validator.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/host.validator.ts
client/src/app/shared/forms/form-validators/index.ts
client/src/app/shared/forms/form-validators/user.ts
server/controllers/api/users.js
server/initializers/installer.js
server/middlewares/validators/users.js
server/models/author.js
server/models/pod.js
server/models/user.js
server/tests/api/check-params/pods.js
server/tests/api/check-params/users.js
server/tests/api/users.js
server/tests/utils/users.js

index f6d360e096b933b866b1913514a94ba785fbddc2..a92f9c23170e058f09e1e45951e980be6eca2b2a 100644 (file)
@@ -14,9 +14,10 @@ export class UserService {
     private restExtractor: RestExtractor
   ) {}
 
-  addUser(username: string, password: string) {
+  addUser(username: string, password: string, email: string) {
     const body = {
       username,
+      email,
       password
     };
 
index 9b76c7c1b95cda3e7a381aed632be6af8090b0c5..105760f48948d4ac09df477d2a7416f0531288ad 100644 (file)
     </div>
   </div>
 
+  <div class="form-group">
+    <label for="email">Email</label>
+    <input
+      type="text" class="form-control" id="email" placeholder="Email"
+      formControlName="email"
+    >
+    <div *ngIf="formErrors.email" class="alert alert-danger">
+      {{ formErrors.email }}
+    </div>
+  </div>
+
   <div class="form-group">
     <label for="password">Password</label>
     <input
index a851fee44f84938f019a763fec45c107525dc7ed..f1d2fde80f177b6993bcd5dd0f23ce61b7ed89d1 100644 (file)
@@ -5,7 +5,12 @@ import { Router } from '@angular/router';
 import { NotificationsService } from 'angular2-notifications';
 
 import { UserService } from '../shared';
-import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared';
+import {
+  FormReactive,
+  USER_USERNAME,
+  USER_EMAIL,
+  USER_PASSWORD
+} from '../../../shared';
 
 @Component({
   selector: 'my-user-add',
@@ -17,10 +22,12 @@ export class UserAddComponent extends FormReactive implements OnInit {
   form: FormGroup;
   formErrors = {
     'username': '',
+    'email': '',
     'password': ''
   };
   validationMessages = {
     'username': USER_USERNAME.MESSAGES,
+    'email': USER_EMAIL.MESSAGES,
     'password': USER_PASSWORD.MESSAGES,
   };
 
@@ -36,6 +43,7 @@ export class UserAddComponent extends FormReactive implements OnInit {
   buildForm() {
     this.form = this.formBuilder.group({
       username: [ '', USER_USERNAME.VALIDATORS ],
+      email:    [ '', USER_EMAIL.VALIDATORS ],
       password: [ '', USER_PASSWORD.VALIDATORS ],
     });
 
@@ -49,9 +57,9 @@ export class UserAddComponent extends FormReactive implements OnInit {
   addUser() {
     this.error = null;
 
-    const { username, password } = this.form.value;
+    const { username, password, email } = this.form.value;
 
-    this.userService.addUser(username, password).subscribe(
+    this.userService.addUser(username, password, email).subscribe(
       () => {
         this.notificationsService.success('Success', `User ${username} created.`);
         this.router.navigate([ '/admin/users/list' ]);
index c27b96e28c32d5611d1d384a7833581794aaeeda..69ae4353d954a075d5b8a9a907dd06dc48a54b96 100644 (file)
@@ -40,6 +40,9 @@ export class UserListComponent {
       username: {
         title: 'Username'
       },
+      email: {
+        title: 'Email'
+      },
       role: {
         title: 'Role',
         sort: false
index 9f2661e12fea19f85a1c61bfde1f12b2f1a05fec..0c8e18a2fdba2a6f2b98d04e65cb7c9d28696a0e 100644 (file)
@@ -26,6 +26,6 @@
   <my-confirm></my-confirm>
 
   <footer>
-    PeerTube, CopyLeft 2015-2016
+    PeerTube, CopyLeft 2015-2017
   </footer>
 </div>
diff --git a/client/src/app/shared/forms/form-validators/email.validator.ts b/client/src/app/shared/forms/form-validators/email.validator.ts
new file mode 100644 (file)
index 0000000..6a2c3bd
--- /dev/null
@@ -0,0 +1,13 @@
+import { FormControl } from '@angular/forms';
+
+export function validateEmail(c: FormControl) {
+  // Thanks to http://emailregex.com/
+  /* tslint:disable */
+  const EMAIL_REGEXP = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+
+  return EMAIL_REGEXP.test(c.value) ? null : {
+    email: {
+      valid: false
+    }
+  };
+}
index ea3e43cb1186812cb5c16b500c98b53ff4201e76..ec417e0795601cd1981ba21963a8ec260d7ecc3d 100644 (file)
@@ -2,7 +2,7 @@ import { FormControl } from '@angular/forms';
 
 export function validateHost(c: FormControl) {
   // Thanks to http://stackoverflow.com/a/106223
-  let HOST_REGEXP = new RegExp(
+  const HOST_REGEXP = new RegExp(
     '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
   );
 
index ab7c2df319441dc18cb76e1dc4b59abed5a19c83..669411a54755ce29c54dac9bbd4ed888eccdb983 100644 (file)
@@ -1,3 +1,4 @@
+export * from './email.validator';
 export * from './host.validator';
 export * from './user';
 export * from './video-abuse';
index 5b11ff265cb195e8c6cf3a08713f24b3da092636..0ad0e2a4b48900c6f050de41d906c857cd57c55b 100644 (file)
@@ -1,5 +1,7 @@
 import { Validators } from '@angular/forms';
 
+import { validateEmail } from './email.validator';
+
 export const USER_USERNAME = {
   VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
   MESSAGES: {
@@ -8,6 +10,13 @@ export const USER_USERNAME = {
     'maxlength': 'Username cannot be more than 20 characters long.'
   }
 };
+export const USER_EMAIL = {
+  VALIDATORS: [ Validators.required, validateEmail ],
+  MESSAGES: {
+    'required': 'Email is required.',
+    'email': 'Email must be valid.',
+  }
+};
 export const USER_PASSWORD = {
   VALIDATORS: [ Validators.required, Validators.minLength(6) ],
   MESSAGES: {
index 6cd0e84f7861046b999dea563bc6eaaabefdff9a..324c99b4cce8ab9d3730ed36d049e160149f50eb 100644 (file)
@@ -61,6 +61,7 @@ function createUser (req, res, next) {
   const user = db.User.build({
     username: req.body.username,
     password: req.body.password,
+    email: req.body.email,
     role: constants.USER_ROLES.USER
   })
 
index fb63b81ac64c1e962446bc4adf283c60f4d6836e..837a987dd0997fa75aae80972f78d5876fcd47c0 100644 (file)
@@ -96,6 +96,7 @@ function createOAuthAdminIfNotExist (callback) {
 
     const username = 'root'
     const role = constants.USER_ROLES.ADMIN
+    const email = constants.CONFIG.ADMIN.EMAIL
     const createOptions = {}
     let password = ''
 
@@ -115,6 +116,7 @@ function createOAuthAdminIfNotExist (callback) {
 
     const userData = {
       username,
+      email,
       password,
       role
     }
index 0629550bcff872df9538fbc0eac4ed58cccc19a0..3089370ffe40ad963e139ef28ab0e155821f3db4 100644 (file)
@@ -13,11 +13,12 @@ const validatorsUsers = {
 function usersAdd (req, res, next) {
   req.checkBody('username', 'Should have a valid username').isUserUsernameValid()
   req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
+  req.checkBody('email', 'Should have a valid email').isEmail()
 
   logger.debug('Checking usersAdd parameters', { parameters: req.body })
 
   checkErrors(req, res, function () {
-    db.User.loadByUsername(req.body.username, function (err, user) {
+    db.User.loadByUsernameOrEmail(req.body.username, req.body.email, function (err, user) {
       if (err) {
         logger.error('Error in usersAdd request validator.', { error: err })
         return res.sendStatus(500)
index f036193c839bb3426c48a73533c75ed56017415c..34b0130971e12db40f7cd4043f2985734d3ba6f5 100644 (file)
@@ -84,7 +84,9 @@ function findOrCreateAuthor (name, podId, userId, transaction, callback) {
   if (transaction) query.transaction = transaction
 
   this.findOrCreate(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
     // [ instance, wasCreated ]
-    return callback(err, result[0])
+    return callback(null, result[0])
   })
 }
index 575ebbc61c693dd5dc65a14b5ad5162504171220..79afb737acbccdcc0827ff131cae6ad8c0dece5f 100644 (file)
@@ -35,7 +35,10 @@ module.exports = function (sequelize, DataTypes) {
       },
       email: {
         type: DataTypes.STRING(400),
-        allowNull: false
+        allowNull: false,
+        validate: {
+          isEmail: true
+        }
       }
     },
     {
index 6cb9eec3fd435ba247061e24194b764c34722796..35a98dd6b430cc725d4bb64bebd155f2fa522ecc 100644 (file)
@@ -32,6 +32,13 @@ module.exports = function (sequelize, DataTypes) {
           }
         }
       },
+      email: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          isEmail: true
+        }
+      },
       role: {
         type: DataTypes.ENUM(values(constants.USER_ROLES)),
         allowNull: false
@@ -42,6 +49,10 @@ module.exports = function (sequelize, DataTypes) {
         {
           fields: [ 'username' ],
           unique: true
+        },
+        {
+          fields: [ 'email' ],
+          unique: true
         }
       ],
       classMethods: {
@@ -52,7 +63,8 @@ module.exports = function (sequelize, DataTypes) {
         list,
         listForApi,
         loadById,
-        loadByUsername
+        loadByUsername,
+        loadByUsernameOrEmail
       },
       instanceMethods: {
         isPasswordMatch,
@@ -88,6 +100,7 @@ function toFormatedJSON () {
   return {
     id: this.id,
     username: this.username,
+    email: this.email,
     role: this.role,
     createdAt: this.createdAt
   }
@@ -151,3 +164,13 @@ function loadByUsername (username, callback) {
 
   return this.findOne(query).asCallback(callback)
 }
+
+function loadByUsernameOrEmail (username, email, callback) {
+  const query = {
+    where: {
+      $or: [ { username }, { email } ]
+    }
+  }
+
+  return this.findOne(query).asCallback(callback)
+}
index 8d52b69b1a96bcdce70f99f98132e135175f6ab2..22cbdb30f7b190afd9e1ef920e38247e1c19ffeb 100644 (file)
@@ -39,7 +39,7 @@ describe('Test pods API validators', function () {
     ], done)
   })
 
-  describe('When making friends', function () {
+  describe('When managing friends', function () {
     let userAccessToken = null
 
     before(function (done) {
@@ -156,13 +156,32 @@ describe('Test pods API validators', function () {
 
     it('Should fail without public key', function (done) {
       const data = {
+        email: 'testexample.com',
         host: 'coucou.com'
       }
       requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
     })
 
+    it('Should fail without an email', function (done) {
+      const data = {
+        host: 'coucou.com',
+        publicKey: 'mysuperpublickey'
+      }
+      requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+    })
+
+    it('Should fail without an invalid email', function (done) {
+      const data = {
+        host: 'coucou.com',
+        email: 'testexample.com',
+        publicKey: 'mysuperpublickey'
+      }
+      requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+    })
+
     it('Should fail without an host', function (done) {
       const data = {
+        email: 'testexample.com',
         publicKey: 'mysuperpublickey'
       }
       requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
@@ -171,6 +190,7 @@ describe('Test pods API validators', function () {
     it('Should fail with an incorrect host', function (done) {
       const data = {
         host: 'http://coucou.com',
+        email: 'testexample.com',
         publicKey: 'mysuperpublickey'
       }
       requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
@@ -185,6 +205,7 @@ describe('Test pods API validators', function () {
     it('Should succeed with the correct parameters', function (done) {
       const data = {
         host: 'coucou.com',
+        email: 'test@example.com',
         publicKey: 'mysuperpublickey'
       }
       requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200)
@@ -193,6 +214,7 @@ describe('Test pods API validators', function () {
     it('Should fail with a host that already exists', function (done) {
       const data = {
         host: 'coucou.com',
+        email: 'test@example.com',
         publicKey: 'mysuperpublickey'
       }
       requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 409)
index c1fcf34a48715817fe0cd68f213ed9df7e6abb50..debf63cf6892d2e391b41eba5871b65412c127f6 100644 (file)
@@ -92,6 +92,7 @@ describe('Test users API validators', function () {
     it('Should fail with a too small username', function (done) {
       const data = {
         username: 'ji',
+        email: 'test@example.com',
         password: 'mysuperpassword'
       }
 
@@ -101,6 +102,7 @@ describe('Test users API validators', function () {
     it('Should fail with a too long username', function (done) {
       const data = {
         username: 'mysuperusernamewhichisverylong',
+        email: 'test@example.com',
         password: 'mysuperpassword'
       }
 
@@ -110,6 +112,26 @@ describe('Test users API validators', function () {
     it('Should fail with an incorrect username', function (done) {
       const data = {
         username: 'my username',
+        email: 'test@example.com',
+        password: 'mysuperpassword'
+      }
+
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+    })
+
+    it('Should fail with a missing email', function (done) {
+      const data = {
+        username: 'ji',
+        password: 'mysuperpassword'
+      }
+
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+    })
+
+    it('Should fail with an invalid email', function (done) {
+      const data = {
+        username: 'mysuperusernamewhichisverylong',
+        email: 'testexample.com',
         password: 'mysuperpassword'
       }
 
@@ -119,6 +141,7 @@ describe('Test users API validators', function () {
     it('Should fail with a too small password', function (done) {
       const data = {
         username: 'myusername',
+        email: 'test@example.com',
         password: 'bla'
       }
 
@@ -128,6 +151,7 @@ describe('Test users API validators', function () {
     it('Should fail with a too long password', function (done) {
       const data = {
         username: 'myusername',
+        email: 'test@example.com',
         password: 'my super long password which is very very very very very very very very very very very very very very' +
                   'very very very very very very very very very very very very very very very veryv very very very very' +
                   'very very very very very very very very very very very very very very very very very very very very long'
@@ -139,6 +163,7 @@ describe('Test users API validators', function () {
     it('Should fail with an non authenticated user', function (done) {
       const data = {
         username: 'myusername',
+        email: 'test@example.com',
         password: 'my super password'
       }
 
@@ -148,6 +173,17 @@ describe('Test users API validators', function () {
     it('Should fail if we add a user with the same username', function (done) {
       const data = {
         username: 'user1',
+        email: 'test@example.com',
+        password: 'my super password'
+      }
+
+      requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409)
+    })
+
+    it('Should fail if we add a user with the same email', function (done) {
+      const data = {
+        username: 'myusername',
+        email: 'user1@example.com',
         password: 'my super password'
       }
 
@@ -157,6 +193,7 @@ describe('Test users API validators', function () {
     it('Should succeed with the correct params', function (done) {
       const data = {
         username: 'user2',
+        email: 'test@example.com',
         password: 'my super password'
       }
 
@@ -166,6 +203,7 @@ describe('Test users API validators', function () {
     it('Should fail with a non admin user', function (done) {
       server.user = {
         username: 'user1',
+        email: 'test@example.com',
         password: 'my super password'
       }
 
@@ -176,6 +214,7 @@ describe('Test users API validators', function () {
 
         const data = {
           username: 'user3',
+          email: 'test@example.com',
           password: 'my super password'
         }
 
index e6d937eb09bbd039d836707f4798390a806cfb7c..df075f48a8a9eedbbfd16d3673b3c970fdecb1cb 100644 (file)
@@ -186,6 +186,7 @@ describe('Test users', function () {
       const user = res.body
 
       expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
       expect(user.id).to.exist
 
       done()
@@ -216,9 +217,11 @@ describe('Test users', function () {
 
       const user = users[0]
       expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
 
       const rootUser = users[1]
       expect(rootUser.username).to.equal('root')
+      expect(rootUser.email).to.equal('admin1@example.com')
       userId = user.id
 
       done()
@@ -238,6 +241,7 @@ describe('Test users', function () {
 
       const user = users[0]
       expect(user.username).to.equal('root')
+      expect(user.email).to.equal('admin1@example.com')
 
       done()
     })
@@ -256,6 +260,7 @@ describe('Test users', function () {
 
       const user = users[0]
       expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
 
       done()
     })
@@ -274,6 +279,7 @@ describe('Test users', function () {
 
       const user = users[0]
       expect(user.username).to.equal('user_1')
+      expect(user.email).to.equal('user_1@example.com')
 
       done()
     })
@@ -291,7 +297,9 @@ describe('Test users', function () {
       expect(users.length).to.equal(2)
 
       expect(users[0].username).to.equal('root')
+      expect(users[0].email).to.equal('admin1@example.com')
       expect(users[1].username).to.equal('user_1')
+      expect(users[1].email).to.equal('user_1@example.com')
 
       done()
     })
index 2bf9c6e3e13f2e7f29359fe130d7c9e62c763291..a2c010f641d41c18a98e0430941c1c42c5aa0c0b 100644 (file)
@@ -20,12 +20,17 @@ function createUser (url, accessToken, username, password, specialStatus, end) {
   }
 
   const path = '/api/v1/users'
+  const body = {
+    username,
+    password,
+    email: username + '@example.com'
+  }
 
   request(url)
     .post(path)
     .set('Accept', 'application/json')
     .set('Authorization', 'Bearer ' + accessToken)
-    .send({ username: username, password: password })
+    .send(body)
     .expect(specialStatus)
     .end(end)
 }