...
 
Commits (2)
......@@ -81,4 +81,21 @@ class User extends Cli\Controller implements Interfaces\CliControllerInterface
$this->out($e);
}
}
public function register_complete()
{
$username = $this->getOpt('username');
if (!$username) {
throw new Exceptions\CliException('Missing username');
}
$user = new Entities\User(strtolower($username));
if (!$user->guid) {
throw new Exceptions\CliException('User does not exist');
}
Core\Events\Dispatcher::trigger('register/complete', 'user', [ 'user' => $user ]);
}
}
......@@ -11,6 +11,7 @@ use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager as EmailConfirmation;
use Minds\Core\Queue\Client as Queue;
use Minds\Entities;
use Minds\Interfaces;
......@@ -79,8 +80,14 @@ class settings implements Interfaces\Api
$user->name = trim($_POST['name']);
}
$emailChange = false;
if (isset($_POST['email']) && $_POST['email']) {
$user->setEmail($_POST['email']);
if (strtolower($_POST['email']) !== strtolower($user->getEmail())) {
$emailChange = true;
}
}
if (isset($_POST['boost_rating'])) {
......@@ -146,6 +153,23 @@ class settings implements Interfaces\Api
$response['status'] = 'error';
}
if ($emailChange) {
/** @var EmailConfirmation $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user);
$reset = $emailConfirmation
->reset();
if ($reset) {
$emailConfirmation
->sendEmail();
} else {
error_log('Cannot reset email confirmation for ' . $user->guid);
}
}
return Factory::response($response);
}
......
<?php
/**
* confirmation
*
* @author edgebal
*/
namespace Minds\Controllers\api\v2\email;
use Minds\Api\Factory;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager;
use Minds\Core\Session;
use Minds\Entities\User;
use Minds\Interfaces;
class confirmation implements Interfaces\Api
{
/**
* GET method
*/
public function get($pages)
{
return Factory::response([]);
}
/**
* POST method
*/
public function post($pages)
{
Factory::isLoggedIn();
/** @var User $user */
$user = Session::getLoggedinUser();
switch ($pages[0] ?? '') {
case 'resend':
try {
/** @var Manager $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user)
->sendEmail();
return Factory::response([
'sent' => true
]);
} catch (\Exception $e) {
return Factory::response([
'status' => 'error',
'message' => $e->getMessage()
]);
}
break;
}
return Factory::response([]);
}
/**
* PUT method
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* DELETE method
*/
public function delete($pages)
{
return Factory::response([]);
}
}
......@@ -4,6 +4,7 @@ namespace Minds\Controllers\api\v2\settings;
use Minds\Api\Factory;
use Minds\Core;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager as EmailConfirmation;
use Minds\Core\Email\EmailSubscription;
use Minds\Entities\User;
use Minds\Interfaces;
......@@ -67,8 +68,27 @@ class emails implements Interfaces\Api
]);
}
$emailChange = strtolower($_POST['email']) !== strtolower($user->getEmail());
$user->setEmail($_POST['email']);
$user->save();
if ($emailChange) {
/** @var EmailConfirmation $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($user);
$reset = $emailConfirmation
->reset();
if ($reset) {
$emailConfirmation
->sendEmail();
} else {
error_log('Cannot reset email confirmation for ' . $user->guid);
}
}
}
if (isset($_POST['notifications'])) {
......
......@@ -106,6 +106,10 @@ class Exported
$exported['MindsEmbed'] = $embedded_entity ?? null;
}
if ($_GET['__e_cnf_token'] ?? false) {
$exported['from_email_confirmation'] = true;
}
return $exported;
}
}
<?php
/**
* Confirmation
*
* @author edgebal
*/
namespace Minds\Core\Email\Campaigns;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Url as ConfirmationUrl;
use Minds\Core\Email\Mailer;
use Minds\Core\Email\Message;
use Minds\Core\Email\Template;
class Confirmation extends EmailCampaign
{
/** @var Template */
protected $template;
/** @var Mailer */
protected $mailer;
/** @var ConfirmationUrl */
protected $confirmationUrl;
/**
* Confirmation constructor.
* @param Template $template
* @param Mailer $mailer
* @param ConfirmationUrl $confirmationUrl
*/
public function __construct(
$template = null,
$mailer = null,
$confirmationUrl = null
) {
$this->template = $template ?: new Template();
$this->mailer = $mailer ?: new Mailer();
$this->confirmationUrl = $confirmationUrl ?: Di::_()->get('Email\Confirmation\Url');
$this->campaign = 'global';
$this->topic = 'confirmation';
}
/**
* @return Message
*/
public function build()
{
$tracking = [
'__e_ct_guid' => $this->user->getGUID(),
'campaign' => $this->campaign,
'topic' => $this->topic,
'state' => 'new',
];
$subject = 'Confirm your Minds email (Action required)';
$this->template->setTemplate('default.tpl');
$this->template->setBody('./Templates/confirmation.tpl');
$this->template->set('user', $this->user);
$this->template->set('username', $this->user->username);
$this->template->set('guid', $this->user->guid);
$this->template->set('tracking', http_build_query($tracking));
$this->template->set(
'confirmation_url',
$this->confirmationUrl
->setUser($this->user)
->generate($tracking)
);
$message = new Message();
$message
->setTo($this->user)
->setMessageId(implode(
'-',
[ $this->user->guid, sha1($this->user->getEmail()), sha1($this->campaign . $this->topic . time()) ]
))
->setSubject($subject)
->setHtml($this->template);
return $message;
}
/**
* @return void
*/
public function send()
{
if ($this->user && $this->user->getEmail()) {
// User is still not enabled
$this->mailer->queue(
$this->build(),
true
);
$this->saveCampaignLog();
}
}
}
......@@ -48,7 +48,7 @@ abstract class EmailCampaign
{
if (
!$this->user
|| !$this->user instanceof \Minds\Entities\User
|| !($this->user instanceof \Minds\Entities\User)
|| $this->user->enabled != 'yes'
|| $this->user->banned === 'yes'
) {
......
<table cellspacing="8" cellpadding="8" border="0" width="600" align="center">
<tbody>
<tr>
<td>
<p>
Please take a moment to validate your email address. This helps us prevent spam and ensure users are real.
Click the button below and we'll know it's you.
</p>
</td>
</tr>
<tr>
<td align="center">
<p>
<a href="<?php echo $vars['confirmation_url'] ?>">
<img src="<?php echo $vars['cdn_assets_url'] ?>assets/emails/cta_complete_setup.png" width="142" alt="Complete Setup"/>
</a>
</p>
</td>
</tr>
<tr>
<td>
<p>
Thanks,<br>
The Minds Team
</p>
</td>
</tr>
<tr>
<td>
<p>Also, be sure to download our mobile app using the links below:</p>
</td>
</tr>
<tr align="center">
<td>
<p>
<a href="https://itunes.apple.com/us/app/minds-com/id961771928?ls=1&mt=8" style="text-decoration: none">
<img src="<?php echo $vars['cdn_assets_url']; ?>assets/ext/appstore.png" width="142" alt="Apple App Store"/>
</a>
<a href="<?php echo "{$vars['site_url']}mobile?{$vars['tracking']}"?>" style="text-decoration: none">
<img src="<?php echo $vars['cdn_assets_url']; ?>assets/photos/minds-android-app.png" width="142" alt="Google Play"/>
</a>
</p>
</td>
</tr>
<tr>
<td>
<p>Thank you for being a pioneer of the free and open internet.</p>
</td>
</tr>
</tbody>
</table>
<?php
/**
* Manager
*
* @author edgebal
*/
namespace Minds\Core\Email\Confirmation;
use Exception;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\Queue\Client as QueueClientFactory;
use Minds\Core\Queue\Interfaces\QueueClient;
use Minds\Entities\User;
use Minds\Entities\UserFactory;
class Manager
{
/** @var Config */
protected $config;
/** @var Jwt */
protected $jwt;
/** @var QueueClient */
protected $queue;
/** @var UserFactory */
protected $userFactory;
/** @var EventsDispatcher */
protected $eventsDispatcher;
/** @var User */
protected $user;
/**
* Manager constructor.
* @param Config $config
* @param Jwt $jwt
* @param QueueClient $queue
* @param UserFactory $userFactory
* @param EventsDispatcher $eventsDispatcher
* @throws Exception
*/
public function __construct(
$config = null,
$jwt = null,
$queue = null,
$userFactory = null,
$eventsDispatcher = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->jwt = $jwt ?: new Jwt();
$this->queue = $queue ?: QueueClientFactory::build();
$this->userFactory = $userFactory ?: new UserFactory();
$this->eventsDispatcher = $eventsDispatcher ?: Di::_()->get('EventsDispatcher');
}
/**
* @param User $user
* @return Manager
*/
public function setUser(User $user): Manager
{
$this->user = $user;
return $this;
}
/**
* @throws Exception
*/
public function sendEmail(): void
{
if (!$this->user) {
throw new Exception('User not set');
}
if ($this->user->isEmailConfirmed()) {
throw new Exception('User email was already confirmed');
}
$config = $this->config->get('email_confirmation');
$now = time();
$expires = $now + $config['expiration'];
$token = $this->jwt
->setKey($config['signing_key'])
->encode([
'user_guid' => (string) $this->user->guid,
'code' => $this->jwt->randomString(),
], $expires, $now);
$this->user
->setEmailConfirmationToken($token)
->save();
$this->eventsDispatcher->trigger('confirmation_email', 'all', [
'user_guid' => (string) $this->user->guid,
'cache' => false,
]);
}
/**
* @return bool
* @throws Exception
*/
public function reset(): bool
{
if (!$this->user) {
throw new Exception('User not set');
}
$this->user
->setEmailConfirmationToken('')
->setEmailConfirmedAt(0);
return (bool) $this->user
->save();
}
/**
* @param string $jwt
* @return bool
* @throws Exception
*/
public function confirm(string $jwt): bool
{
$config = $this->config->get('email_confirmation');
if ($this->user) {
throw new Exception('Confirmation user is inferred from JWT');
}
$confirmation = $this->jwt
->setKey($config['signing_key'])
->decode($jwt); // Should throw if expired
if (
!$confirmation ||
!$confirmation['user_guid'] ||
!$confirmation['code']
) {
throw new Exception('Invalid JWT');
}
$user = $this->userFactory->build($confirmation['user_guid'], false);
if (!$user || !$user->guid) {
throw new Exception('Invalid user');
} elseif ($user->isEmailConfirmed()) {
throw new Exception('User email was already confirmed');
}
$data = $this->jwt
->setKey($config['signing_key'])
->decode($user->getEmailConfirmationToken());
if (
$data['user_guid'] !== $confirmation['user_guid'] ||
$data['code'] !== $confirmation['code']
) {
throw new Exception('Invalid confirmation token data');
}
$user
->setEmailConfirmationToken('')
->setEmailConfirmedAt(time())
->save();
$this->queue
->setQueue('WelcomeEmail')
->send([
'user_guid' => (string) $user->guid,
]);
return true;
}
}
<?php
/**
* Url
*
* @author edgebal
*/
namespace Minds\Core\Email\Confirmation;
use Minds\Core\Config;
use Minds\Core\Di\Di;
use Minds\Entities\User;
class Url
{
/** @var string */
const EMAIL_CONFIRMATION_PATH = '/email-confirmation';
/** @var Config */
protected $config;
/** @var User */
protected $user;
/**
* ConfirmationUrlDelegate constructor.
* @param Config $config
*/
public function __construct(
$config = null
) {
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @param User $user
* @return Url
*/
public function setUser(User $user): Url
{
$this->user = $user;
return $this;
}
/**
* @param array $params
* @return string
*/
public function generate(array $params = []): string
{
return sprintf(
'%s%s?%s',
rtrim($this->config->get('site_url'), '/'),
static::EMAIL_CONFIRMATION_PATH,
http_build_query(array_merge($params, [
'__e_cnf_token' => $this->user->getEmailConfirmationToken(),
])),
);
}
}
<?php
/**
* ConfirmationSender
*
* @author edgebal
*/
namespace Minds\Core\Email\Delegates;
use Minds\Core\Email\Campaigns\Confirmation;
use Minds\Entities\User;
use Minds\Interfaces\SenderInterface;
class ConfirmationSender implements SenderInterface
{
/**
* @param User $user
*/
public function send(User $user)
{
(new Confirmation())
->setUser($user)
->send();
}
}
......@@ -52,11 +52,15 @@ class Events
Dispatcher::register('welcome_email', 'all', function ($opts) {
$this->sendCampaign(new Delegates\WelcomeSender(), $opts->getParameters());
});
Dispatcher::register('confirmation_email', 'all', function ($opts) {
$this->sendCampaign(new Delegates\ConfirmationSender(), $opts->getParameters());
});
}
private function sendCampaign(SenderInterface $sender, $params)
{
$user = new User($params['user_guid']);
$user = new User($params['user_guid'], $params['cache'] ?? true);
$sender->send($user);
}
}
......@@ -37,7 +37,7 @@ class Mailer
$this->mailer->SMTPAuth = true;
$this->mailer->Username = Core\Config::_()->email['smtp']['username'];
$this->mailer->Password = Core\Config::_()->email['smtp']['password'];
$this->mailer->SMTPSecure = "ssl";
$this->mailer->SMTPSecure = Core\Config::_()->email['smtp']['smtp_secure'] ?? "ssl";
$this->mailer->Port = Core\Config::_()->email['smtp']['port'];
}
......
......@@ -41,5 +41,13 @@ class Provider extends DiProvider
$this->di->bind('Email\CampaignLogs\Repository', function ($di) {
return new CampaignLogs\Repository();
}, ['useFactory' => true]);
$this->di->bind('Email\Confirmation', function ($di) {
return new Confirmation\Manager();
}, ['useFactory' => true]);
$this->di->bind('Email\Confirmation\Url', function ($di) {
return new Confirmation\Url();
}, ['useFactory' => true]);
}
}
......@@ -2,13 +2,29 @@
namespace Minds\Core\Email;
use Exception;
use Minds\Core\Analytics\Metrics\Event;
use Minds\Core\Di\Di;
use Minds\Core\Email\Confirmation\Manager as ConfirmationManager;
use Minds\Core\Session;
class RouterHooks
{
public function __construct($event = null)
/** @var Event */
protected $event;
/** @var ConfirmationManager */
protected $confirmationManager;
/**
* RouterHooks constructor.
* @param Event $event
* @param ConfirmationManager $confirmationManager
*/
public function __construct($event = null, $confirmationManager = null)
{
$this->event = $event ?: new Event();
$this->confirmationManager = $confirmationManager ?: Di::_()->get('Email\Confirmation');
}
public function withRouterRequest($request)
......@@ -18,6 +34,17 @@ class RouterHooks
$action = 'email:clicks';
if (strpos($path, '/emails/unsubscribe') !== false) {
$action = 'email:unsubscribe';
} elseif (isset($queryParams['__e_cnf_token'])) {
try {
$this->confirmationManager
->confirm($queryParams['__e_cnf_token']);
} catch (Exception $e) {
// Do not continue processing.
// TODO: Log?
return;
}
$action = 'email:confirm';
}
$platform = isset($queryParams['cb']) ? 'mobile' : 'browser';
if (isset($queryParams['platform'])) {
......
......@@ -55,17 +55,22 @@ class Register
if ($params['user']->captcha_failed) {
return false;
}
//send welcome email
try {
/** @var Core\Email\Confirmation\Manager $emailConfirmation */
$emailConfirmation = Di::_()->get('Email\Confirmation');
$emailConfirmation
->setUser($params['user'])
->sendEmail();
} catch (\Exception $e) {
error_log((string) $e);
}
try {
Core\Queue\Client::build()->setQueue('Registered')
->send([
'user_guid' => $params['user']->guid,
'user_guid' => (string) $params['user']->guid,
]);
//Delay by 15 minutes (aws max) so the user has time to complete their profile
Core\Queue\Client::build()->setQueue('WelcomeEmail')
->send([
'user_guid' => $params['user']->guid,
], 900);
} catch (\Exception $e) {
}
});
......
......@@ -259,6 +259,14 @@ class Defaults
];
});
// Do not index email confirmation and redirect OG to /
Manager::add(Core\Email\Confirmation\Url::EMAIL_CONFIRMATION_PATH, function ($slugs = []) {
return [
'og:url' => $this->config->site_url,
'robots' => 'noindex'
];
});
Manager::add('/wallet/tokens/transactions', function ($slugs = []) {
$meta = [
'title' => 'Transactions Ledger',
......
......@@ -60,6 +60,8 @@ class User extends \ElggUser
$this->attributes['onchain_booster'] = null;
$this->attributes['toaster_notifications'] = 1;
$this->attributes['mode'] = ChannelMode::OPEN;
$this->attributes['email_confirmation_token'] = null;
$this->attributes['email_confirmed_at'] = null;
parent::initializeAttributes();
}
......@@ -637,6 +639,50 @@ class User extends \ElggUser
return (bool) $this->boost_autorotate;
}
/**
* @param string $token
* @return User
*/
public function setEmailConfirmationToken(string $token): User
{
$this->email_confirmation_token = $token;
return $this;
}
/**
* @return string|null
*/
public function getEmailConfirmationToken(): ?string
{
return ((string) $this->email_confirmation_token) ?: null;
}
/**
* @param int $time
* @return User
*/
public function setEmailConfirmedAt(?int $time): User
{
$this->email_confirmed_at = $time;
return $this;
}
/**
* @return int|null
*/
public function getEmailConfirmedAt(): ?int
{
return ((int) $this->email_confirmed_at) ?: null;
}
/**
* @return bool
*/
public function isEmailConfirmed(): bool
{
return (bool) $this->email_confirmed_at;
}
/**
* Subscribes user to another user.
*
......@@ -869,6 +915,10 @@ class User extends \ElggUser
$export['deleted'] = $this->getDeleted();
}
$export['email_confirmed'] =
(!$this->getEmailConfirmationToken() && !$this->getEmailConfirmedAt()) || // Old users poly-fill
$this->isEmailConfirmed();
$export['eth_wallet'] = $this->getEthWallet() ?: '';
$export['rating'] = $this->getRating();
......
<?php
/**
* UserFactory.
*
* @author edgebal
*/
namespace Minds\Entities;
use Exception;
class UserFactory
{
/**
* @param null $guid
* @param bool $cache
* @return User|null
*/
public function build($guid = null, bool $cache = true): ?User
{
try {
return new User($guid, $cache);
} catch (Exception $e) {
error_log((string) $e);
return null;
}
}
}
<?php
namespace Spec\Minds\Core\Email\Confirmation;
use Exception;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Email\Confirmation\Manager;
use Minds\Core\Events\EventsDispatcher;
use Minds\Core\Queue\Interfaces\QueueClient;
use Minds\Entities\User;
use Minds\Entities\UserFactory;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
/** @var Config */
protected $config;
/** @var Jwt */
protected $jwt;
/** @var QueueClient */
protected $queue;
/** @var UserFactory */
protected $userFactory;
/** @var EventsDispatcher */
protected $eventsDispatcher;
public function let(
Config $config,
Jwt $jwt,
QueueClient $queue,
UserFactory $userFactory,
EventsDispatcher $eventsDispatcher
) {
$this->config = $config;
$this->jwt = $jwt;
$this->queue = $queue;
$this->userFactory = $userFactory;
$this->eventsDispatcher = $eventsDispatcher;
$this->config->get('email_confirmation')
->willReturn([
'expiration' => 30,
'signing_key' => '~key~'
]);
$this->beConstructedWith($config, $jwt, $queue, $userFactory, $eventsDispatcher);
}
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_send_email(
User $user
) {
$user->isEmailConfirmed()
->shouldBeCalled()
->willReturn(false);
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$user->get('guid')
->shouldBeCalled()
->willReturn(1000);
$this->jwt->randomString()
->shouldBeCalled()
->willReturn('~random~');
$this->jwt->encode([
'user_guid' => '1000',
'code' => '~random~',
], Argument::type('int'), Argument::type('int'))
->shouldBeCalled()
->willReturn('~token~');
$user->setEmailConfirmationToken('~token~')
->shouldBeCalled()
->willReturn($user);
$user->save()
->shouldBeCalled()
->willReturn(true);
$this->eventsDispatcher->trigger('confirmation_email', 'all', [
'user_guid' => '1000',
'cache' => false
])
->shouldBeCalled()
->willReturn(true);
$this
->setUser($user)
->shouldNotThrow(Exception::class)
->duringSendEmail();
}
public function it_should_throw_if_no_user_during_send_email()
{
$this
->shouldThrow(new Exception('User not set'))
->duringSendEmail();
}
public function it_should_throw_if_email_is_confirmed_during_send_email(
User $user
) {
$user->isEmailConfirmed()
->shouldBeCalled()
->willReturn(true);
$user->save()
->shouldNotBeCalled();
$this
->setUser($user)
->shouldThrow(new Exception('User email was already confirmed'))
->duringSendEmail();
}
public function it_should_reset(
User $user
) {
$user->setEmailConfirmationToken('')
->shouldBeCalled()
->willReturn($user);
$user->setEmailConfirmedAt(0)
->shouldBeCalled()
->willReturn($user);
$user->save()
->shouldBeCalled()
->willReturn(true);
$this
->setUser($user)
->reset()
->shouldReturn(true);
}
public function it_should_throw_if_no_user_during_reset()
{
$this
->shouldThrow(new Exception('User not set'))
->duringReset();
}
public function it_should_confirm(
User $user
) {
$this->jwt->setKey('~key~')
->shouldBeCalledTimes(2)
->willReturn($this->jwt);
$this->jwt->decode('~token~')
->shouldBeCalledTimes(2)
->willReturn([
'user_guid' => '1000',
'code' => 'phpspec',
]);
$this->userFactory->build('1000', false)
->shouldBeCalled()
->willReturn($user);
$user->get('guid')
->shouldBeCalled()
->willReturn(1000);
$user->isEmailConfirmed()
->shouldBeCalled()
->willReturn(false);
$user->getEmailConfirmationToken()
->shouldBeCalled()
->willReturn('~token~');
$user->setEmailConfirmationToken('')
->shouldBeCalled()
->willReturn($user);
$user->setEmailConfirmedAt(Argument::type('int'))
->shouldBeCalled()
->willReturn($user);
$user->save()
->shouldBeCalled()
->willReturn(true);
$this->queue->setQueue('WelcomeEmail')
->shouldBeCalled()
->willReturn($this->queue);
$this->queue->send([
'user_guid' => '1000'
])
->shouldBeCalled()
->willReturn(true);
$this
->confirm('~token~')
->shouldReturn(true);
}
public function it_should_throw_if_user_is_set_during_confirm(
User $user
) {
$this
->setUser($user)
->shouldThrow(new Exception('Confirmation user is inferred from JWT'))
->duringConfirm('~token~');
}
public function it_should_throw_if_jwt_is_invalid_during_confirm()
{
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->decode('~token~')
->shouldBeCalled()
->willReturn([
]);
$this
->shouldThrow(new Exception('Invalid JWT'))
->duringConfirm('~token~');
}
public function it_should_throw_if_invalid_user_during_confirm(
User $user
) {
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->decode('~token~')
->shouldBeCalled()
->willReturn([
'user_guid' => '1000',
'code' => 'phpspec',
]);
$this->userFactory->build('1000', false)
->shouldBeCalled()
->willReturn($user);
$user->get('guid')
->shouldBeCalled()
->willReturn(null);
$this
->shouldThrow(new Exception('Invalid user'))
->duringConfirm('~token~');
}
public function it_should_throw_if_email_is_confirmed_during_confirm(
User $user
) {
$this->jwt->setKey('~key~')
->shouldBeCalled()
->willReturn($this->jwt);
$this->jwt->decode('~token~')
->shouldBeCalled()
->willReturn([
'user_guid' => '1000',
'code' => 'phpspec',
]);
$this->userFactory->build('1000', false)
->shouldBeCalled()
->willReturn($user);
$user->get('guid')
->shouldBeCalled()
->willReturn($user);
$user->isEmailConfirmed()
->shouldBeCalled()
->willReturn(true);
$this
->shouldThrow(new Exception('User email was already confirmed'))
->duringConfirm('~token~');
}
public function it_should_throw_if_invalid_token_data_during_confirm(
User $user
) {
$this->jwt->setKey('~key~')
->shouldBeCalledTimes(2)
->willReturn($this->jwt);
$this->jwt->decode('~token~')
->shouldBeCalled()
->willReturn([
'user_guid' => '1000',
'code' => 'phpspec',
]);
$this->userFactory->build('1000', false)
->shouldBeCalled()
->willReturn($user);
$user->get('guid')
->shouldBeCalled()
->willReturn($user);
$user->isEmailConfirmed()
->shouldBeCalled()
->willReturn(false);
$user->getEmailConfirmationToken()
->shouldBeCalled()
->willReturn('~token.2~');
$this->jwt->decode('~token.2~')
->shouldBeCalled()
->willReturn([
'user_guid' => '1001',
'code' => 'phpspec_fail',
]);
$this
->shouldThrow(new Exception('Invalid confirmation token data'))
->duringConfirm('~token~');
}
}
<?php
namespace Spec\Minds\Core\Email\Confirmation;
use Minds\Core\Config;
use Minds\Core\Email\Confirmation\Url;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class UrlSpec extends ObjectBehavior
{
/** @var Config */
protected $config;
public function let(
Config $config
) {
$this->config = $config;
$this->beConstructedWith($config);
}
public function it_is_initializable()
{
$this->shouldHaveType(Url::class);
}
public function it_should_generate(
User $user
) {
$this->config->get('site_url')
->shouldBeCalled()
->willReturn('https://phpspec.minds.test/');
$user->getEmailConfirmationToken()
->shouldBeCalled()
->willReturn('~token~');
$this
->setUser($user)
->generate(['test' => 1, 'phpspec' => 'yes'])
->shouldReturn('https://phpspec.minds.test/email-confirmation?test=1&phpspec=yes&__e_cnf_token=%7Etoken%7E');
}
}
......@@ -2,6 +2,7 @@
namespace Spec\Minds\Core\Email;
use Minds\Core\Email\Confirmation\Manager as ConfirmationManager;
use Minds\Core\Email\RouterHooks;
use PhpSpec\ObjectBehavior;
use Minds\Core\Analytics\Metrics\Event;
......@@ -12,10 +13,13 @@ class RouterHooksSpec extends ObjectBehavior
{
private $event;
public function let(Event $event)
private $confirmationManager;
public function let(Event $event, ConfirmationManager $confirmationManager)
{
$this->beConstructedWith($event);
$this->beConstructedWith($event, $confirmationManager);
$this->event = $event;
$this->confirmationManager = $confirmationManager;
}
public function it_is_initializable()
......
......@@ -609,3 +609,8 @@ $CONFIG->set('upgrades', [
]
],
]);
$CONFIG->set('email_confirmation', [
'signing_key' => '',
'expiration' => 172800, // 48 hours
]);