1

I have been playing around with CI4 and Shield for authentication and authorisation. There is very little information out there on the work flow for registering new users and how to notify the user that they have been registered into the app. Any help would appreciated?

Here is my controller function so far:

type// Add a member
    public function add_member()
    {
        $data = [];
        //$users = model('UserModel');

        if ($this->request->getMethod() == 'post') {
            $rules = [
                //'membership_status' =>
                'firstname' => 'required|min_length[3]|max_length[50]',
                'lastname' => 'required|min_length[3]|max_length[50]',
                'prefered_name' => 'min_length[3]|max_length[50]',
                'gender' => 'required|min_length[3]|max_length[25]',
                'date_of_birth' => 'required|valid_date',
                'address_1' => 'required|min_length[3]|max_length[100]',
                'address_2' => 'required|min_length[3]|max_length[100]',
                'postal_code' => 'required|min_length[3]|max_length[10]|integer',
                'city' => 'required|min_length[3]|max_length[50]',
                'home_phone' => 'min_length[3]|max_length[50]',
                'work_phone' => 'min_length[3]|max_length[50]',
                'mobile' => 'required_without[home_phone,work_phone]|min_length[3]|max_length[50]',
                'consent' => 'min_length[2]|max_length[50]',
                //'email' => 'required|min_length[6]|max_length[50]|valid_email',
            ];

            if (!$this->validate($rules)) {
                $data['validation'] = $this->validator;
                
                $this->session->set('Details', $_POST);
                //dd($_SESSION);
            } else {
                $newData = [
                    //'id' => $id,
                    'membership_status' => 'Active',
                    'firstname' => $this->request->getVar('firstname', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'lastname' => $this->request->getVar('lastname', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'prefered_name' => $this->request->getVar('prefered_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'date_joined' => date('Y-m-d'),
                    'gender' => $this->request->getVar('gender', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'date_of_birth' => $this->request->getVar('date_of_birth'),
                    'address_1' => $this->request->getVar('address_1', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'address_2' => $this->request->getVar('address_2', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'postal_code' => $this->request->getVar('postal_code', FILTER_SANITIZE_NUMBER_INT),
                    'city' => $this->request->getVar('city', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
                    'home_phone' => $this->request->getVar('home_phone', FILTER_SANITIZE_NUMBER_INT),
                    'work_phone' => $this->request->getVar('work_phone', FILTER_SANITIZE_NUMBER_INT),
                    'mobile' => $this->request->getVar('mobile', FILTER_SANITIZE_NUMBER_INT),
                    'consent' => $this->request->getVar('consent'),
                    //'email' => $this->request->getVar('email', FILTER_SANITIZE_EMAIL)
                ];

                $user = new User([
                    'username' => NULL,
                    'email'    => $this->request->getVar('email', FILTER_SANITIZE_EMAIL),
                    'password' => $this->request->getVar('email', FILTER_SANITIZE_EMAIL),
                ]);
                $this->users->save($user);
                $newData['id'] = $this->users->getInsertID();
                // To get the complete user object with ID, we need to get from the database
                $user = $this->users->findById($this->users->getInsertID());

                // Add to default group
                $this->users->addToDefaultGroup($user);

                $this->users->save($newData);
                
                $userEmail['email'] = $user->getEmail();

                // Send the user an email with the code
                $email = emailer()->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? '');
                $email->setTo($user->email);
                $email->setSubject(lang('DojoApp.mailSubject'));
                $email->setMessage($this->view('email/add_new_user_email', $userEmail));

                if ($email->send(false) === false) {
                    log_message('error', $email->printDebugger(['headers']));

                    return redirect()->route('login')->with('error', lang('Auth.unableSendEmailToUser', [$user->email]));
                }

                // Clear the email
                $email->clear();

                $this->session->setFlashdata('success', 'Successfuly added new member information');
                return redirect()->to('/admin/members');
            }
        }
        echo view('templates/header');
        echo view('admin/left_nav_bar');
        echo view('admin/add_member', $data);
        echo view('admin/left_nav_bar_closure');
        echo view('admin/javascript');
        echo view('templates/footer');
    } here

No information on the topic internet wide.

nzdev01
  • 13
  • 2

1 Answers1

2

Looks like you might not be looking in the right place for this.


FIRST: Make sure you have completed the setup - Composer will be your best option


  1. Installation Link
  2. Setup Link

Using this LINK you can see the flow for adding users. Looks like you are missing the 2 lines below: Here is the snippet from that link you may have overlooked during your search:

use CodeIgniter\Shield\Entities\User;
use CodeIgniter\Events\Events;

// Get the User Provider (UserModel by default)
$users = auth()->getProvider();

$user = new User([
    'username' => 'foo-bar',
    'email'    => 'foo.bar@example.com',
    'password' => 'secret plain text password',
]);
$users->save($user);

// To get the complete user object with ID, we need to get from the database
$user = $users->findById($users->getInsertID());

// Add to default group
$users->addToDefaultGroup($user);

Once you have added the user using the above method or the default registration form provided by Shield, an email will get sent to the new user. Below is the section on the same site describing the actions needed:

Note You need to configure app/Config/Email.php to allow Shield to send emails. See Installation.

By default, once a user registers they have an active account that can be used. You can enable Shield's built-in, email-based activation flow within the Auth config file.

public array $actions = [
    'register' => \CodeIgniter\Shield\Authentication\Actions\EmailActivator::class,
    'login'    => null,
];

Additional information on Authentication Actions can be found here. This will allow you to configure more if needed and defines the two provided by Shield.

Ok, Let's get to work!

app/Config/Events.php We need to add an event to send the new user an email

....
use CodeIgniter\I18n\Time;
use CodeIgniter\Shield\Exceptions\LogicException;
use CodeIgniter\Shield\Exceptions\RuntimeException;

// notice I am passing two variables from the controller $user and $tmpPass
// I will force the user to change password on first login
Events::on('newRegistration', static function ($user, $tmpPass) {

    $userEmail = $user->email;
    if ($userEmail === null) {
        throw new LogicException(
            'Email Activation needs user email address. user_id: ' . $user->id
        );
    }

    $date      = Time::now()->toDateTimeString();

    // Send the email
    $email = emailer()->setFrom(setting('Email.fromEmail'), setting('Email.fromName') ?? '');
    $email->setTo($userEmail);
    $email->setSubject(lang('Auth.emailActivateSubject'));
    $email->setMessage(view(setting('Auth.views')['email_manual_activate_email'], ['userEmail' => $userEmail,'tmpPass' => $tmpPass, 'date' => $date]));

    if ($email->send(false) === false) {
        throw new RuntimeException('Cannot send email for user: ' . $user->email . "\n" . $email->printDebugger(['headers']));
    }

    // Clear the email
    $email->clear();

});

app/Controllers/Users.php

use \CodeIgniter\Events\Events;
use \CodeIgniter\Config\Factories;

// modify however you want. I use Ajax...
    public function add() {

        checkAjax();

        if (!$this->user->hasPermission('users.create')) {
            $response['success']        = false;
            $response['messages']       = lang("App.invalid_permission");
            return $this->response->setJSON($response);
        }

        $response = array();

        $fields['username']         = $this->request->getPost('username');
        $fields['password']         = $this->request->getPost('password');
        $fields['email']            = $this->request->getPost('email');
        $fields['group']            = $this->request->getPost('group');

        $this->validation->setRules([
            'username'              => ['label' => 'Username', 'rules'          => 'required|max_length[30]|min_length[3]|regex_match[/\A[a-zA-Z0-9\.]+\z/]|is_unique[users.username,id,{id}]'],
            'password'              => ['label' => 'Password', 'rules'          => 'required|min_length[8]'],
            'email'                 => ['label' => 'Email Address', 'rules'     => 'required|valid_email|is_unique[auth_identities.secret,id,{id}]'],
            'group'                 => ['label' => 'Group', 'rules'             => 'required'],
        ]);

        if ($this->validation->run($fields) == FALSE) {

            $response['success']    = false;
            $response['messages']   = $this->validation->getErrors(); //Show Error in Input Form
        } else {

            $users                          = auth()->getProvider();
            $user                           = new User([
                'username'                  => $fields['username'],
                'email'                     => $fields['email'],
                'password'                  => $fields['password'],
                'status'                    => 'OFF',
                'status_message'            => 'New User',
            ]);
            // save the user with the above information
            $users->save($user);
            // get the new ID as we still have work to do
            $user                           = $users->findById($users->getInsertID());
            // set the flag to make user change password on first login
            $user->forcePasswordReset();
            // make sure this is the only group(s) for the user
            $user->syncGroups($fields['group']);

            // Additional work done here..
            $actionClass                    = setting('Auth.actions')['register'] ?? null;
            $action                         = Factories::actions($actionClass)->createIdentity($user);
            $code                           = $action; // do not need this yet though it is set
            $tmpPass                        = $fields['password'];
            // trigger our new Event and send the two variables 
            $confirm                        = Events::trigger('newRegistration', $user, $tmpPass);

            // if eveything went well, notifuy the Admin the user has been added and email sent
            if ($confirm) {
                $response['success']        = true;
                $response['messages']       = lang("App.insert-success");
            } else {
                $response['success']        = false;
                $response['messages']       = lang("App.insert-error");
            }
        }
        return $this->response->setJSON($response);
    }

email template -> CodeIgniter\Shield\Views\Email\email_manual_activate_email

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<head>
    <meta name="x-apple-disable-message-reformatting">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title><?= lang('Auth.emailActivateSubject') ?></title>
</head>

<body>
    <p><?= lang('App.emailActivateMail') ?></p>
    <div style="text-align: center">
        <h3><?= site_url(); ?></h3>
        <p>User ID: <?= $userEmail?></p>
        <p>Temporary Password: <?= $tmpPass; ?></p>
    </div>
    <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
        <tbody>
            <tr>
                <td style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;" align="left" width="100%" height="20">
                    &#160;
                </td>
            </tr>
        </tbody>
    </table>
    <b><?= lang('Auth.emailInfo') ?></b>
    <p><?= lang('Auth.emailDate') ?> <?= esc($date) ?><br>
    Email System Generated for a New User.</p>
</body>

</html>

Make sure you add this line in app/Config/Auth.php

 public array $views = [
'email_manual_activate_email' => '\CodeIgniter\Shield\Views\Email\email_manual_activate_email',
    ];

Now your new users will receive an email when you set them up, even if

$allowRegistration = false;

Once they login for the first time, it will also send them a new code. Verifying the code will send them to wherever you have the force_reset redirect set to.

With all of the extra data you are entering into the tables, you can either expand the users table, or create a new table. More information on that here with code to assist.

DeanE10
  • 123
  • 7
  • Right, just wondering how will the new user know that the been added (I guess through a standard email) and how will they login? Maybe through a magic link which takes them to a form to put in their password? just no clear flow from what I have read in the docs regarding whats next – nzdev01 May 20 '23 at 04:48
  • Looks like you updated your code above... I updated the above to add `Events::trigger('register', $user);` since you are registering them manually. remove the email code you have as the system Event will use `email_activate_email.php` by default. You can modify that file as needed. I also added more notes and links. Please ensure you are correctly installed and setup. Also, emails will NOT be sent if registration is turned off in `app/config/Auth.php - $allowRegistration = true;` or not setup as described above and registering them manually. - Good Luck! – DeanE10 May 20 '23 at 16:06
  • Thank you for the reply. I have removed my custom email processes and add the event trigger. Email system is working fine for authentication and verification. The event from Events::trigger('register', $user); is not triggering an email, while actions have been configured as in the docs: public array $actions = [ 'register' => \CodeIgniter\Shield\Authentication\Actions\EmailActivator::class, 'login' => null, ]; – nzdev01 May 21 '23 at 05:38
  • public bool $allowRegistration = true; is set correctly, magic links are sent etc. The logs shows no errors or warnings – nzdev01 May 21 '23 at 05:39
  • ok... Added the FULL code above to show fully functioning process on my side. Emails come through perfectly when adding a user manually via Admin. – DeanE10 May 21 '23 at 17:51
  • This update definitely provided the correct direction. I have implemented the bits I needed and now it works well, thank you @DeanE10. – nzdev01 May 22 '23 at 08:28