diff --git a/app/assets/less/application.less b/app/assets/less/application.less
index 850a879..d851765 100644
--- a/app/assets/less/application.less
+++ b/app/assets/less/application.less
@@ -33,6 +33,7 @@
@import "components/button";
@import "components/badge";
@import "components/section";
+@import "components/spacer";
@import "components/pagination";
@import "components/blankslate";
@import "components/request-item";
@@ -42,6 +43,7 @@
@import "views/landingpage";
@import "views/about";
@import "views/login";
+@import "views/register";
// Plugins
@import "vendor/ionicons/ionicons";
diff --git a/app/assets/less/components/spacer.less b/app/assets/less/components/spacer.less
new file mode 100644
index 0000000..b1084d2
--- /dev/null
+++ b/app/assets/less/components/spacer.less
@@ -0,0 +1,6 @@
+
+.spacer {
+ display: block;
+ border-bottom: 1px solid @gray-light;
+ margin: @spacer-margin;
+}
diff --git a/app/assets/less/variables.less b/app/assets/less/variables.less
index d4930f7..f3a7c1d 100644
--- a/app/assets/less/variables.less
+++ b/app/assets/less/variables.less
@@ -154,6 +154,13 @@
@blankslate-spacing: 25px 10px;
@blankslate-spacing-sm: 15px 5px;
+// ----------------------------------
+// Spacer
+// ----------------------------------
+
+@spacer-color: @gray-light;
+@spacer-margin: 1em 0;
+
// ----------------------------------
// Pagination
// ----------------------------------
diff --git a/app/assets/less/views/register.less b/app/assets/less/views/register.less
new file mode 100644
index 0000000..1cbe1c3
--- /dev/null
+++ b/app/assets/less/views/register.less
@@ -0,0 +1,6 @@
+
+.register {
+ &:extend(.section all);
+ .center-block();
+ width: 60%;
+}
diff --git a/app/config/routes.yml b/app/config/routes.yml
index 11703ff..4c36381 100644
--- a/app/config/routes.yml
+++ b/app/config/routes.yml
@@ -43,6 +43,9 @@ router:
oauth-disconnect-confirm:
pattern: '/oauth/{provider:([a-z]+)}/disconnect/{confirm}'
path: 'User::oauthdisconnect'
+ user-register:
+ pattern: '/register'
+ path: Auth::register
user-settings:
pattern: '/settings'
path:
diff --git a/app/controllers/AuthController.php b/app/controllers/AuthController.php
index ec97e5f..012185b 100644
--- a/app/controllers/AuthController.php
+++ b/app/controllers/AuthController.php
@@ -2,9 +2,12 @@
namespace App\Controller;
-use App\Controller\ControllerBase;
+use App\Controller\ControllerBase,
+ App\Model\Data\User,
+ App\Form\Login as LoginForm,
+ App\Form\Registration as RegistrationForm;
-use App\Form\Login as LoginForm;
+use Httpcb\OAuth\UserData\UserDataInterface;
class AuthController extends ControllerBase
{
@@ -78,17 +81,21 @@ class AuthController extends ControllerBase
else {
$result = $this->auth->loginOauth($data);
- // There was an error when creating the account
- if (is_array($result)) {
- $msg = '';
- foreach ($result as $message) {
- $msg .= '
' . $message->getMessage() . ' ';
+ if ($result === false) {
+
+ if (User::findFirstByEmail($data->getEmail())) {
+ $this->flash->error('The email address is already in use.');
+ $this->response->redirect('/login');
+ return;
}
- $this->flash->message('error', "Failed to create account: ");
- $this->response->redirect('/login');
- } else {
- $this->response->redirect('/');
+
+ $this->session->set('auth:register:data', $data);
+ $this->response->redirect(['for' => 'user-register']);
+ return;
}
+
+ // User is logged in.
+ $this->response->redirect('/');
}
} catch(\Exception $e) {
$this->flash->message('error', 'Failed to authenticate.');
@@ -106,6 +113,42 @@ class AuthController extends ControllerBase
}
}
+ public function registerAction()
+ {
+ $data = $this->session->get('auth:register:data');
+ if (!($data instanceof UserDataInterface)) {
+ $this->response->redirect('/');
+ return;
+ }
+
+ $user = new User();
+ $user->assign($data->toArray(), null,
+ [ 'email', 'username', 'firstname', 'lastname' ]);
+
+ $form = new RegistrationForm($user);
+
+ if ($this->request->isPost()) {
+
+ $formData = $this->request->getPost();
+ if ($form->isValid($formData)) {
+ $user->setOAuthId($data->getProvider(), $data->getId());
+ if ($user->save()) {
+ $this->auth->systemLogin($user);
+ $this->flash->success('User successfully created. Now add your first callback!');
+ $this->response->redirect('/callback/new');
+ } else {
+ $this->flash->error('Could not create user');
+ }
+ }
+ $form->setEntity($formData);
+ } else {
+ $form->isValid($data->toArray());
+ }
+
+ $this->view->provider = $data->getProvider();
+ $this->view->form = $form;
+ }
+
public function logoutAction()
{
$this->auth->clearIdentity();
diff --git a/app/forms/Registration.php b/app/forms/Registration.php
new file mode 100644
index 0000000..f6d5160
--- /dev/null
+++ b/app/forms/Registration.php
@@ -0,0 +1,107 @@
+setValidation(new Validation());
+
+ // Username
+ $username = new Text('username', array(
+ 'class' => 'form-control',
+ 'placeholder' => 'Username',
+ ));
+
+ $username->setLabel('Username');
+
+ $username->addValidators([
+ new AlnumValidator([
+ 'message' => 'Username must contain only letters and numbers.'
+ ]),
+ new StringLengthValidator([
+ 'min' => 2,
+ 'messageMinimum' => 'Username must be at least :min characters long.',
+ ]),
+ new CallbackValidator([
+ 'callback' => function($data) { return User::findFirstByUsername($data['username']) === false; },
+ 'message' => 'The username already exists.',
+ 'attribute' => 'username',
+ ])
+ ]);
+
+ $this->add($username);
+
+ // Names
+ foreach([ 'first-name' => 'Firstname', 'last-name' => 'Lastname' ] as $id => $label) {
+
+ $name = new Text($id, array(
+ 'class' => 'form-control',
+ 'placeholder' => $label,
+ ));
+
+ $name->setLabel($label);
+ $name->addValidator(new AlphaValidator([
+ 'allowSpace' => false,
+ 'allowEmpty' => true,
+ ]));
+
+ $this->add($name);
+ }
+
+ // Email
+ $email = new Text('email', array(
+ 'class' => 'form-control',
+ 'placeholder' => 'Email',
+ 'readonly' => '',
+ ));
+
+ $email->addValidators([
+ new IdenticalValidator([
+ 'accepted' => $user->getEmail(),
+ ]),
+ new CallbackValidator([
+ 'callback' => function($data) { return User::findFirstByEmail($data['email']) === false; },
+ 'message' => 'This email already exist.',
+ ])
+ ]);
+
+ $email->setLabel('Email');
+ $this->add($email);
+
+ // Submit
+ $submit = new Submit('submit', array('class' => 'button button-success', 'value' => 'Register'));
+ $this->add($submit);
+ }
+}
diff --git a/app/library/Auth.php b/app/library/Auth.php
index 2ec49ae..5adfc8c 100644
--- a/app/library/Auth.php
+++ b/app/library/Auth.php
@@ -44,26 +44,15 @@ class Auth extends Component
* Login using OAuth
*
* @param UserDataInterface $data
- * @return bool|\Phalcon\Mvc\Model\MessageInterface[]
+ * @return bool
*/
public function loginOauth(UserDataInterface $data)
{
$user = User::findFirstByOAuthID($data);
+ // Did not find any user.
if (!$user) {
- // Did not find any user. create him.
- $user = User::createFromOAuthData($data);
-
- if ($user->save() === false) {
- return $user->getMessages();
- }
- }
- // Here we activate the user.
- // As for OAuth we perform registration if the user does not exist.
- // We should therefore activate deleted accounts.
- else if ($user->Status == User::STATUS_DELETED) {
- $user->Status = User::STATUS_ACTIVE;
- $user->save();
+ return false;
}
$this->setIdentity($user->getId());
@@ -74,6 +63,17 @@ class Auth extends Component
return true;
}
+ /**
+ * The system logs in a user (without credentials).
+ *
+ * @param User $user
+ */
+ public function systemLogin(User $user)
+ {
+ $this->setIdentity($user->getId());
+ $this->eventsManager->fire('auth:onLogin', $this, 'System');
+ }
+
/**
* @param $identity
* @return Auth
diff --git a/app/library/Form.php b/app/library/Form.php
new file mode 100644
index 0000000..ebbb8bc
--- /dev/null
+++ b/app/library/Form.php
@@ -0,0 +1,70 @@
+ 'control-label',
+ 'class' => [ 'col-sm-10' ],
+ 'message' => ''
+ ];
+
+ $ele = $this->get($name);
+
+ if (isset($opt['label-length'])) {
+ $length = (int) $opt['label-length'];
+ } else {
+ $length = 2;
+ }
+ $options['label-class'] .= ' col-sm-' . $length;
+
+ if (isset($opt['length'])) {
+
+ $len = $opt['length'];
+
+ if ($len === 'full') {
+ $options['class'] = [];
+ } else {
+ $options['class'] = [ 'col-sm-' . $len ];
+ }
+
+ unset($opt['length']);
+ }
+
+ if ($ele->hasMessages()) {
+ $options['class'][] = 'has-error';
+ $options['message'] = $ele->getMessages()->current();
+ }
+
+ return $this->_render($ele, $options);
+ }
+
+ protected function _render(FormElement $ele, $opt)
+ {
+ $xhtml = '';
+
+ if (strlen($ele->getLabel()) > 0) {
+
+ $xhtml .= sprintf(
+ '%s ',
+ $opt['label-class'], $ele->getName(), $ele->getLabel());
+ }
+
+ $xhtml .= ''
+ . $ele->render();
+
+ if (strlen($opt['message']) > 0) {
+ $xhtml .= '' . $opt['message'] . ' ';
+ }
+
+ $xhtml .= '
';
+
+ return $xhtml;
+ }
+}
diff --git a/app/library/OAuth/UserData/Github.php b/app/library/OAuth/UserData/Github.php
index a465770..0de7fea 100644
--- a/app/library/OAuth/UserData/Github.php
+++ b/app/library/OAuth/UserData/Github.php
@@ -2,67 +2,18 @@
namespace Httpcb\OAuth\UserData;
-class Github implements UserDataInterface
+class Github extends UserData
{
- protected $data;
+ protected $_provider = 'Github';
/**
* {@inheritDoc}
*/
public function __construct(array $data)
{
- $this->data = $data;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getProvider()
- {
- return 'Github';
- }
-
- /**
- * {@inheritDoc}
- */
- public function getId()
- {
- return (int) $this->data['id'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getUsername()
- {
- return $this->data['login'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return $this->data['name'];
- }
-
- public function getFirstname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), 0, $pos) : $this->getName();
- }
-
- public function getLastname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), $pos+1) : null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getEmail()
- {
- return $this->data['email'];
+ $this->_id = $data['id'];
+ $this->_username = $data['login'];
+ $this->_name = $data['name'];
+ $this->_email = $data['email'];
}
}
diff --git a/app/library/OAuth/UserData/Gitlab.php b/app/library/OAuth/UserData/Gitlab.php
index 85f3653..9ea6d41 100644
--- a/app/library/OAuth/UserData/Gitlab.php
+++ b/app/library/OAuth/UserData/Gitlab.php
@@ -2,72 +2,18 @@
namespace Httpcb\OAuth\UserData;
-class Gitlab implements UserDataInterface
+class Gitlab extends UserData
{
- protected $data;
+ protected $_provider = 'Gitlab';
/**
* {@inheritDoc}
*/
public function __construct(array $data)
{
- $this->data = $data;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getProvider()
- {
- return 'Gitlab';
- }
-
- /**
- * {@inheritDoc}
- */
- public function getId()
- {
- return (int) $this->data['id'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getUsername()
- {
- return $this->data['username'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return $this->data['name'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getFirstname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), 0, $pos) : $this->getName();
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLastname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), $pos+1) : null;
- }
- /**
- * {@inheritDoc}
- */
- public function getEmail()
- {
- return $this->data['email'];
+ $this->_id = $data['id'];
+ $this->_username = $data['username'];
+ $this->_name = $data['name'];
+ $this->_email = $data['email'];
}
}
diff --git a/app/library/OAuth/UserData/Google.php b/app/library/OAuth/UserData/Google.php
index 28aa7a1..096215e 100644
--- a/app/library/OAuth/UserData/Google.php
+++ b/app/library/OAuth/UserData/Google.php
@@ -2,76 +2,20 @@
namespace Httpcb\OAuth\UserData;
-class Google implements UserDataInterface
+class Google extends UserData
{
- protected $data;
+ protected $_provider = 'Google';
/**
* {@inheritDoc}
*/
public function __construct(array $data)
{
- $this->data = $data;
- }
+ $this->_id = $data['id'];
+ $this->_name = $data['displayName'];
- /**
- * {@inheritDoc}
- */
- public function getProvider()
- {
- return 'Google';
- }
-
- /**
- * {@inheritDoc}
- */
- public function getId()
- {
- return (int) $this->data['id'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getUsername()
- {
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- return $this->data['displayName'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getFirstname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), 0, $pos) : $this->getName();
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLastname()
- {
- $pos = strpos($this->getName(), ' ');
- return $pos !== false ? substr($this->getName(), $pos+1) : null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getEmail()
- {
- if (isset($this->data['emails'][0]['value'])) {
- return $this->data['emails'][0]['value'];
+ if (isset($data['emails'][0]['value'])) {
+ $this->_email = $data['emails'][0]['value'];
}
- return null;
}
}
diff --git a/app/library/OAuth/UserData/LinkedIn.php b/app/library/OAuth/UserData/LinkedIn.php
index 0907453..6f1d969 100644
--- a/app/library/OAuth/UserData/LinkedIn.php
+++ b/app/library/OAuth/UserData/LinkedIn.php
@@ -2,78 +2,18 @@
namespace Httpcb\OAuth\UserData;
-class LinkedIn implements UserDataInterface
+class LinkedIn extends UserData
{
- protected $data;
+ protected $_provider = 'LinkedIn';
/**
* {@inheritDoc}
*/
public function __construct(array $data)
{
- $this->data = $data;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getProvider()
- {
- return 'LinkedIn';
- }
-
- /**
- * {@inheritDoc}
- */
- public function getId()
- {
- return $this->data['id'];
- }
-
- /**
- * {@inheritDoc}
- */
- public function getUsername()
- {
- return null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getName()
- {
- $name = '';
- if ($this->getFirstname() !== null) {
- $name = $this->getFirstname();
- }
- if ($this->getLastname() !== null) {
- $name .= ' ' . $this->getLastname();
- }
- return $name;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getFirstname()
- {
- return isset($this->data['firstName']) ? $this->data['firstName'] : null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getLastname()
- {
- return isset($this->data['lastName']) ? $this->data['lastName'] : null;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getEmail()
- {
- return $this->data['emailAddress'];
+ $this->_id = $data['id'];
+ $this->_firstname = isset($data['firstName']) ? $data['firstName'] : null;
+ $this->_lastname = isset($data['lastName']) ? $data['lastName'] : null;
+ $this->_email = $data['emailAddress'];
}
}
diff --git a/app/library/OAuth/UserData/UserData.php b/app/library/OAuth/UserData/UserData.php
new file mode 100644
index 0000000..00ba85c
--- /dev/null
+++ b/app/library/OAuth/UserData/UserData.php
@@ -0,0 +1,104 @@
+_provider;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getId()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getUsername()
+ {
+ return $this->_username;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getName()
+ {
+ if ($this->_name === null && strlen($this->_firstname) > 0) {
+ $name = $this->_firstname;
+ if (strlen($this->_lastname) > 0) {
+ $name .= ' ' . $this->_lastname;
+ }
+ return $name;
+ }
+ return $this->_name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getFirstname()
+ {
+ if ($this->_firstname === null) {
+ $pos = strpos($this->getName(), ' ');
+ return $pos !== false ? substr($this->getName(), 0, $pos) : $this->getName();
+
+ }
+ return $this->_firstname;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getLastname()
+ {
+ if ($this->_lastname === null) {
+ $pos = strpos($this->getName(), ' ');
+ return $pos !== false ? substr($this->getName(), $pos+1) : null;
+ }
+ return $this->_lastname;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getEmail()
+ {
+ return $this->_email;
+ }
+
+ public function toArray()
+ {
+ $data = [];
+ foreach(get_class_methods($this) as $method) {
+ if (substr($method, 0, 3) == 'get') {
+ $field = lcfirst(substr($method, 3));
+ $data[$field] = $this->$method();
+ }
+ }
+ return $data;
+ }
+}
diff --git a/app/library/OAuth/UserData/UserDataInterface.php b/app/library/OAuth/UserData/UserDataInterface.php
index 456b72d..358cafb 100644
--- a/app/library/OAuth/UserData/UserDataInterface.php
+++ b/app/library/OAuth/UserData/UserDataInterface.php
@@ -54,4 +54,9 @@ interface UserDataInterface
* @return string
*/
public function getEmail();
+
+ /**
+ * @return array
+ */
+ public function toArray();
}
diff --git a/app/models/Data/User.php b/app/models/Data/User.php
index 22a7df7..d813e8b 100644
--- a/app/models/Data/User.php
+++ b/app/models/Data/User.php
@@ -4,7 +4,7 @@ namespace App\Model\Data;
use Phalcon\Mvc\Model,
Phalcon\Validation,
- Phalcon\Validation\Validator\Uniqueness,
+ Phalcon\Validation\Validator\Callback as CallbackValidator,
InvalidArgumentException,
Httpcb\OAuth\UserData\UserDataInterface;
@@ -52,8 +52,14 @@ class User extends Model
// Validation
$validator = new Validation();
- $validator->add('username', new Uniqueness(['message' => 'The username already exists.']));
- $validator->add('email', new Uniqueness(['message' => 'The email address already exists.']));
+ $validator->add('username', new CallbackValidator([
+ 'callback' => function() { return $this->findFirstByUsername($this->getUsername()) === false; },
+ 'message' => 'The username already exists.'
+ ]));
+ $validator->add('email', new CallbackValidator([
+ 'callback' => function() { return $this->findFirstByEmail($this->getEmail()) === false; },
+ 'message' => 'The email address already exists.'
+ ]));
return $this->validate($validator);
}
@@ -295,6 +301,20 @@ class User extends Model
return $this->linkedin_id;
}
+ /**
+ * @param string $provider
+ * @param string $id
+ * @return $this
+ */
+ public function setOAuthId($provider, $id)
+ {
+ $method = 'set' . ucfirst($provider) . 'Id';
+ if (method_exists($this, $method)) {
+ $this->$method($id);
+ }
+ return $this;
+ }
+
/**
* @param string $id
* @return User
@@ -339,8 +359,24 @@ class User extends Model
static public function findFirstByUsernameOrEmail($value)
{
return self::findFirst([
- "(email = :v: OR username = :v:) AND status = :s:",
- "bind" => [ 'v' => $value, 's' => self::STATUS_ACTIVE ]
+ "(email = :v: OR username = :v:) AND status != :s:",
+ "bind" => [ 'v' => $value, 's' => self::STATUS_DELETED ]
+ ]);
+ }
+
+ static public function findFirstByEmail($email)
+ {
+ return self::findFirst([
+ "email = :email: AND status != :s:",
+ "bind" => [ 'email' => $email, 's' => self::STATUS_DELETED ]
+ ]);
+ }
+
+ static public function findFirstByUsername($username)
+ {
+ return self::findFirst([
+ "username = :username: AND status != :s:",
+ "bind" => [ 'username' => $username, 's' => self::STATUS_DELETED ]
]);
}
@@ -349,8 +385,8 @@ class User extends Model
$column = strtolower($oauth->getProvider());
return self::findFirst([
- "{$column}_id = :id:",
- "bind" => [ 'id' => $oauth->getId() ]
+ "{$column}_id = :id: AND status != :s:",
+ "bind" => [ 'id' => $oauth->getId(), 's' => self::STATUS_DELETED ]
]);
}
diff --git a/app/views/auth/register.volt b/app/views/auth/register.volt
new file mode 100644
index 0000000..7ae2c3d
--- /dev/null
+++ b/app/views/auth/register.volt
@@ -0,0 +1,43 @@
+
+
+
+
+
Account registration
+
+
+
+
Information!
+
+
+ The form is prepared with the information provided by {{ provider }} .
+ Please check the information and make changes if necessary before continue.
+
+
+
+
+
+
+
+