diff --git a/app/templates/client/app/views/reset-password.html b/app/templates/client/app/views/reset-password.html
new file mode 100644
index 0000000..00615dd
--- /dev/null
+++ b/app/templates/client/app/views/reset-password.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+ New password has been set, please click on "Back to login".
+
+
+
Reset your password
+
Enter your new password.
+
+
+
+
+
+
+
diff --git a/app/templates/client/app/views/signup.html b/app/templates/client/app/views/signup.html
new file mode 100644
index 0000000..a7ea7a6
--- /dev/null
+++ b/app/templates/client/app/views/signup.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
Sign Up
+
Please fill up your details.
+
+
+
+
+
+
+
diff --git a/app/templates/client/bower.json b/app/templates/client/bower.json
index 1ce01bd..d53a29c 100644
--- a/app/templates/client/bower.json
+++ b/app/templates/client/bower.json
@@ -23,6 +23,6 @@
},
"appPath": "app",
"resolutions": {
- "angular": "1.3.16"
+ "angular": "1.3.18"
}
}
diff --git a/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info
new file mode 100644
index 0000000..0cea5b9
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.info
@@ -0,0 +1,4 @@
+name = Skeleton General
+core = 7.x
+package = Skeleton
+features[features_api][] = api:2
diff --git a/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module
new file mode 100644
index 0000000..1b1aae6
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_general/skeleton_general.module
@@ -0,0 +1,43 @@
+uid));
+ }
+}
+
+/**
+ * Create a token and send it to the user.
+ *
+ * @param $message_type
+ * The type of the message to send.
+ * @param $account
+ * The user account.
+ */
+function skeleton_send_token_to_user($message_type, $account) {
+ $controller = new RestfulTokenAuthController('restful_token_auth');
+ $token = $controller->generateAccessToken($account->uid);
+
+ // Sending Email with instructions to the user.
+ skeleton_notify_user($message_type, $account, $token->token);
+}
+
+/**
+ * Send an email to the user with the token.
+ *
+ * @param $message_type
+ * The type of the message.
+ * @param $account
+ * The account of the user to notify.
+ * @param $token
+ * The token of the user.
+ */
+function skeleton_notify_user($message_type, $account, $token) {
+ $message = message_create($message_type, array('arguments' => array('@token' => $token)), $account);
+ $wrapper = entity_metadata_wrapper('message', $message);
+ message_notify_send_message($wrapper->value(), array('mail' => $account->mail));
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc
new file mode 100644
index 0000000..5684270
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.features.inc
@@ -0,0 +1,74 @@
+ "1");
+ }
+}
+
+/**
+ * Implements hook_default_message_type().
+ */
+function skeleton_messages_default_message_type() {
+ $items = array();
+ $items['reset_password'] = entity_import('message_type', '{
+ "name" : "reset_password",
+ "description" : "Reset Password",
+ "argument_keys" : [],
+ "argument" : [],
+ "category" : "message_type",
+ "data" : {
+ "token options" : { "clear" : 0 },
+ "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" }
+ },
+ "language" : "",
+ "arguments" : null,
+ "message_text" : { "und" : [
+ {
+ "value" : "Skeleton - Reset Password",
+ "format" : "filtered_html",
+ "safe_value" : "\\u003Cp\\u003ESkeleton - Reset Password\\u003C\\/p\\u003E\\n"
+ },
+ {
+ "value" : "Hello,\\r\\n\\r\\nClick this link to reset your password:\\r\\n[skeleton:reset-password-link]",
+ "format" : "filtered_html",
+ "safe_value" : "\\u003Cp\\u003EHello,\\u003C\\/p\\u003E\\n\\u003Cp\\u003EClick this link to reset your password:\\u003Cbr \\/\\u003E\\n[skeleton:reset-password-link]\\u003C\\/p\\u003E\\n"
+ }
+ ]
+ }
+ }');
+ $items['verify_email'] = entity_import('message_type', '{
+ "name" : "verify_email",
+ "description" : "Verify Email",
+ "argument_keys" : [],
+ "argument" : [],
+ "category" : "message_type",
+ "data" : {
+ "token options" : { "clear" : 0 },
+ "purge" : { "override" : 0, "enabled" : 0, "quota" : "", "days" : "" }
+ },
+ "language" : "",
+ "arguments" : null,
+ "message_text" : { "und" : [
+ {
+ "value" : "Skeleton - Verify Email",
+ "format" : "filtered_html",
+ "safe_value" : "\\u003Cp\\u003ESkeleton - Verify Email\\u003C\\/p\\u003E\\n"
+ },
+ {
+ "value" : "Hello,\\r\\n\\r\\nClick this link to verify your email:\\r\\n[skeleton:verify-email-link]",
+ "format" : "filtered_html",
+ "safe_value" : "\\u003Cp\\u003EHello,\\u003C\\/p\\u003E\\n\\u003Cp\\u003EClick this link to verify your email:\\u003Cbr \\/\\u003E\\n[skeleton:verify-email-link]\\u003C\\/p\\u003E\\n"
+ }
+ ]
+ }
+ }');
+ return $items;
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info
new file mode 100644
index 0000000..2fda594
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.info
@@ -0,0 +1,14 @@
+name = Skeleton Messages
+core = 7.x
+package = Skeleton
+dependencies[] = ctools
+dependencies[] = entity
+dependencies[] = message
+dependencies[] = message_notify
+dependencies[] = strongarm
+features[ctools][] = strongarm:strongarm:1
+features[features_api][] = api:2
+features[message_type][] = reset_password
+features[message_type][] = verify_email
+features[variable][] = field_bundle_settings_message__reset_password
+features[variable][] = field_bundle_settings_message__verify_email
diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module
new file mode 100644
index 0000000..99a4c75
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.module
@@ -0,0 +1,55 @@
+ t('Reset Password Link'),
+ 'description' => t('Display the link for resetting a password.'),
+ );
+
+ $info['types']['skeleton'] = array(
+ 'name' => t('Skeleton'),
+ 'description' => t('Tokens related to Skeleton.'),
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function skeleton_messages_tokens($type, $tokens, array $data = array(), array $options = array()) {
+
+ $replacements = array();
+ if ($type != 'skeleton') {
+ return $replacements;
+ }
+
+ foreach ($tokens as $name => $original) {
+ if (!in_array($name, array('reset-password-link', 'verify-email-link'))) {
+ continue;
+ }
+
+ $message = reset($data);
+ $token = $message->arguments['@token'];
+
+ $path = str_replace('-link', '', $name);
+
+ $options = array(
+ 'fragment' => $path . '/' . $token,
+ );
+ $replacements[$original] = url(variable_get('skeleton_default_client_domain', 'http://localhost:9000/'), $options);
+ }
+
+ return $replacements;
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc
new file mode 100644
index 0000000..4966099
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_messages/skeleton_messages.strongarm.inc
@@ -0,0 +1,82 @@
+disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */
+ $strongarm->api_version = 1;
+ $strongarm->name = 'field_bundle_settings_message__reset_password';
+ $strongarm->value = array(
+ 'view_modes' => array(),
+ 'extra_fields' => array(
+ 'form' => array(),
+ 'display' => array(
+ 'message__message_text__0' => array(
+ 'message_notify_email_body' => array(
+ 'weight' => '1',
+ 'visible' => FALSE,
+ ),
+ 'message_notify_email_subject' => array(
+ 'weight' => '0',
+ 'visible' => TRUE,
+ ),
+ ),
+ 'message__message_text__1' => array(
+ 'message_notify_email_body' => array(
+ 'weight' => '0',
+ 'visible' => TRUE,
+ ),
+ 'message_notify_email_subject' => array(
+ 'weight' => '1',
+ 'visible' => FALSE,
+ ),
+ ),
+ ),
+ ),
+ );
+ $export['field_bundle_settings_message__reset_password'] = $strongarm;
+
+ $strongarm = new stdClass();
+ $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */
+ $strongarm->api_version = 1;
+ $strongarm->name = 'field_bundle_settings_message__verify_email';
+ $strongarm->value = array(
+ 'view_modes' => array(),
+ 'extra_fields' => array(
+ 'form' => array(),
+ 'display' => array(
+ 'message__message_text__0' => array(
+ 'message_notify_email_subject' => array(
+ 'visible' => TRUE,
+ 'weight' => 0,
+ ),
+ 'message_notify_email_body' => array(
+ 'visible' => FALSE,
+ 'weight' => 0,
+ ),
+ ),
+ 'message__message_text__1' => array(
+ 'message_notify_email_subject' => array(
+ 'visible' => FALSE,
+ 'weight' => 0,
+ ),
+ 'message_notify_email_body' => array(
+ 'visible' => TRUE,
+ 'weight' => 0,
+ ),
+ ),
+ ),
+ ),
+ );
+ $export['field_bundle_settings_message__verify_email'] = $strongarm;
+
+ return $export;
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php
new file mode 100644
index 0000000..a37fa09
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/SkeletonRestfulEmptyResponse.php
@@ -0,0 +1,23 @@
+ array(
+ \RestfulInterface::POST => 'resetPassword',
+ ),
+ );
+
+ /**
+ * Send "Reset Password" email.
+ */
+ public function resetPassword() {
+
+ $email = $this->request['email'];
+
+ if (!$account = user_load_by_mail($email)) {
+ throw new \RestfulBadRequestException('Email does\'t exists.');
+ }
+
+ skeleton_send_token_to_user('reset_password', $account);
+
+ throw new \SkeletonRestfulEmptyResponse();
+ }
+
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc
new file mode 100644
index 0000000..0914209
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/reset_password/reset_password.inc
@@ -0,0 +1,22 @@
+ t('Reset Password'),
+ 'description' => t('Sending a reset password link to the user.'),
+ 'name' => 'reset_password',
+ 'resource' => 'reset-password',
+ 'class' => 'SkeletonResetPasswordResource',
+ 'rate_limit' => array(
+ // The 'request' event is the basic event. You can declare your own events.
+ 'request' => array(
+ 'event' => 'request',
+ // Rate limit is cleared every day.
+ 'period' => new \DateInterval('P1D'),
+ 'limits' => array(
+ 'authenticated user' => 10,
+ 'anonymous user' => 10,
+ 'administrator' => \RestfulRateLimitManager::UNLIMITED_RATE_LIMIT,
+ ),
+ ),
+ ),
+);
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php
new file mode 100644
index 0000000..6eb62f3
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/SkeletonUsersResource.class.php
@@ -0,0 +1,101 @@
+ 'pass',
+ 'access_callbacks' => array(
+ array($this, 'denyAccessOnGET'),
+ ),
+ );
+
+ $public_fields['mail'] = array(
+ 'property' => 'mail',
+ );
+
+ $public_fields['name'] = array(
+ 'property' => 'name',
+ );
+
+ // Prevent from trying to set the user status on creation.
+ if ($this->getMethod() != \RestfulBase::POST) {
+ $public_fields['status'] = array(
+ 'property' => 'status',
+ );
+ }
+
+ return $public_fields;
+ }
+ /**
+ * Deny access when the request method is "GET".
+ */
+ protected function denyAccessOnGET() {
+ return $this->getMethod() == \REstfulInterface::GET ? \RestfulInterface::ACCESS_DENY : \RestfulInterface::ACCESS_ALLOW;
+ }
+
+ /**
+ * Checks if the token is valid for this user.
+ * We need to verify it because this resource is not authenticated require.
+ *
+ * @return bool
+ */
+ protected function checkPatchAccess() {
+ $controller = new RestfulAuthenticationToken('restful_token_auth');
+ return $controller->authenticate($this->getRequest(), $this->getMethod());
+ }
+
+ /**
+ * @param $op
+ * @param $entity_type
+ * @param $entity
+ *
+ * @return bool
+ */
+ public function checkEntityAccess($op, $entity_type, $entity) {
+ if ($this->getMethod() == \RestfulBase::PATCH) {
+ return $this->checkPatchAccess();
+ }
+ if ($this->getMethod() == \RestfulBase::POST) {
+ return $this->visitorsRegistrationAccess();
+ }
+ return parent::checkEntityAccess($op, $entity_type, $entity);
+ }
+
+ /**
+ * @param string $op
+ * @param string $public_field_name
+ * @param \EntityMetadataWrapper $property_wrapper
+ * @param \EntityMetadataWrapper $wrapper
+ *
+ * @return bool
+ */
+ public function checkPropertyAccess($op, $public_field_name, EntityMetadataWrapper $property_wrapper, EntityMetadataWrapper $wrapper) {
+ if ($this->getMethod() == \RestfulBase::PATCH) {
+ return $this->checkPatchAccess();
+ }
+ if ($this->getMethod() == \RestfulBase::POST) {
+ return $this->visitorsRegistrationAccess();
+ }
+ return parent::checkPropertyAccess($op, $public_field_name, $property_wrapper, $wrapper);
+ }
+
+ /**
+ * Determine if user registration is allowed for visitors by checking the
+ * site settings.
+ *
+ * @return bool
+ */
+ public function visitorsRegistrationAccess() {
+ return variable_get('user_register', USER_REGISTER_ADMINISTRATORS_ONLY) != USER_REGISTER_ADMINISTRATORS_ONLY;
+ }
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc
new file mode 100644
index 0000000..87b97cb
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/update/1.1/update__1_1.inc
@@ -0,0 +1,26 @@
+ t('Update User Entity'),
+ 'resource' => 'users',
+ 'name' => 'update__1_1',
+ 'entity_type' => 'user',
+ 'bundle' => 'user',
+ 'description' => t('Users creation endpoint.'),
+ 'class' => 'SkeletonUsersResource',
+ // Set the minor version.
+ 'minor_version' => 1,
+ 'rate_limit' => array(
+ // The 'request' event is the basic event. You can declare your own events.
+ 'request' => array(
+ 'event' => 'request',
+ // Rate limit is cleared every day.
+ 'period' => new \DateInterval('P1D'),
+ 'limits' => array(
+ 'authenticated user' => 10,
+ 'anonymous user' => 5,
+ 'administrator' => \RestfulRateLimitManager::UNLIMITED_RATE_LIMIT,
+ ),
+ ),
+ ),
+);
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php
new file mode 100644
index 0000000..06dc34a
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/SkeletonUsersAvailabilityResource.class.php
@@ -0,0 +1,68 @@
+ array(
+ \RestfulInterface::GET => 'available',
+ ),
+ );
+
+ /**
+ * `RestfulBase` enforce to declare this function.
+ *
+ * @throws \RestfulEntityViewMode
+ *
+ * @return array
+ * An empty array.
+ */
+ public function publicFieldsInfo() {
+ return array();
+ }
+
+ /**
+ * Allow only specific keys from the request.
+ */
+ public function getKeys() {
+ $request = $this->getRequest();
+
+ // Remove unnecessary keys.
+ foreach (array_keys($request) as $key) {
+
+ if (!in_array($key, array('name', 'mail'))) {
+ unset($request[$key]);
+ }
+ }
+
+ return $request;
+ }
+
+
+ /**
+ * Checks availability.
+ * Assuming the class that inherit from here implemented the vars:
+ * - "field"
+ * - "load_account_by"
+ *
+ * @return array
+ */
+ public function available() {
+ $request = $this->getKeys();
+ $available = array();
+
+ foreach ($request as $key => $value) {
+ $function = 'user_load_by_' . $key;
+ $account = $function($value);
+ $available[$key] = !is_null($value) && empty($account);
+ }
+
+ return array('available' => $available);
+ }
+}
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc
new file mode 100644
index 0000000..f5363d5
--- /dev/null
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/plugins/restful/user/users-availability/users_availability.inc
@@ -0,0 +1,9 @@
+ t('Check user availability.'),
+ 'description' => t('Checks if an email address and/or username not registered.'),
+ 'name' => 'users_availability',
+ 'resource' => 'users-availability',
+ 'class' => 'SkeletonUsersAvailabilityResource',
+);
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info
index 9f666f6..705f4fc 100644
--- a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.info
@@ -7,3 +7,4 @@ dependencies[] = entity_validator
dependencies[] = skeleton_company
files[] = plugins/restful/node/SkeletonEntityBaseNode.php
+files[] = plugins/restful/SkeletonRestfulEmptyResponse.php
diff --git a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module
index 2498879..9a4ff93 100644
--- a/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module
+++ b/app/templates/skeleton/modules/custom/skeleton_restful/skeleton_restful.module
@@ -19,3 +19,33 @@ function skeleton_restful_ctools_plugin_directory($module, $plugin) {
}
}
+/**
+ * Implements hook_entity_property_info_alter().
+ */
+function skeleton_restful_entity_property_info_alter(&$info) {
+ $info['user']['bundles']['user']['properties']['pass'] = array(
+ 'label' => t('Password'),
+ 'setter callback' => 'skeleton_restful_set_user_pass',
+ 'schema field' => 'pass',
+ );
+
+ $info['user']['bundles']['user']['properties']['mail'] = array(
+ 'label' => t('Email'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'schema field' => 'mail',
+ );
+
+ $info['user']['bundles']['user']['properties']['name'] = array(
+ 'label' => t('Username'),
+ 'setter callback' => 'entity_property_verbatim_set',
+ 'schema field' => 'name',
+ );
+}
+
+/**
+ * Setter callback; Set user password.
+ */
+function skeleton_restful_set_user_pass($account, $name, $value) {
+ require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
+ $account->pass = user_hash_password($value);
+}
diff --git a/app/templates/skeleton/skeleton.info b/app/templates/skeleton/skeleton.info
index c12d5f7..139e6ae 100644
--- a/app/templates/skeleton/skeleton.info
+++ b/app/templates/skeleton/skeleton.info
@@ -46,7 +46,9 @@ dependencies[] = views
dependencies[] = views_bulk_operations
; Skeleton
+dependencies[] = skeleton_general
dependencies[] = skeleton_company
dependencies[] = skeleton_event
dependencies[] = skeleton_file
dependencies[] = skeleton_restful
+dependencies[] = skeleton_messages
diff --git a/app/templates/skeleton/skeleton.profile b/app/templates/skeleton/skeleton.profile
index 93baabf..acd25ca 100644
--- a/app/templates/skeleton/skeleton.profile
+++ b/app/templates/skeleton/skeleton.profile
@@ -58,6 +58,8 @@ function skeleton_setup_variables() {
'restful_file_upload' => 1,
// Files settings.
'file_default_scheme' => 'public',
+ // Prevent from Drupal to send an email to user after verification.
+ 'user_mail_status_activated_notify' => FALSE,
);
foreach ($variables as $key => $value) {