Skip to content

Commit 617409f

Browse files
romanetarsmarcet
andauthored
OTP FLOW Tweaks (#45)
* change OTP input and layout tweaks Signed-off-by: romanetar <[email protected]> * otp flow change for users without password Signed-off-by: romanetar <[email protected]> * set password email reminder on otp login Signed-off-by: romanetar <[email protected]> * fix: refactored code Change-Id: Ieedb7e7be88d84030c1995e6e482ea355f9e6066 * fix: check new user condition to send OTP reminder Change-Id: I547db8f9b308776e201aee1c9907c7ad21ae7c11 --------- Signed-off-by: romanetar <[email protected]> Co-authored-by: [email protected] <[email protected]>
1 parent 249a2c9 commit 617409f

18 files changed

+475
-161
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,6 @@ AUTH_ALLOWS_NATIVE_AUTH=1
107107
AUTH_ALLOWS_OTP=1
108108
AUTH_ALLOWS_NATIVE_AUTH_CONFIG=1
109109
MAIL_SEND_WELCOME_EMAIL=1
110-
DEFAULT_PROFILE_IMAGE=
110+
DEFAULT_PROFILE_IMAGE=
111+
112+
AUTH_PASSWORD_RESET_LIFETIME=1800

app/Http/Controllers/Auth/ResetPasswordController.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
use App\libs\Auth\Repositories\IUserPasswordResetRequestRepository;
1616
use App\Services\Auth\IUserService;
1717
use Auth\Exceptions\UserPasswordResetRequestVoidException;
18-
use Auth\Repositories\IUserRepository;
1918
use Illuminate\Support\Facades\Log;
20-
use Illuminate\Support\Facades\Redirect;
21-
use Illuminate\Support\Facades\URL;
2219
use Illuminate\Support\Facades\Validator;
2320
use Illuminate\Http\Request as LaravelRequest;
2421
use models\exceptions\EntityNotFoundException;

app/Http/Controllers/UserController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ public function getAccount()
255255
return $this->ok(
256256
[
257257
'pic' => $user->getPic(),
258-
'full_name' => $user->getFullName()
258+
'full_name' => $user->getFullName(),
259+
'has_password_set' => $user->hasPasswordSet()
259260
]
260261
);
261262
} catch (ValidationException $ex) {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php namespace App\Jobs;
2+
/*
3+
* Copyright 2024 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
15+
use App\Services\Auth\IUserService as IAuthUserService;
16+
use Auth\User;
17+
use Illuminate\Bus\Queueable;
18+
use Illuminate\Contracts\Queue\ShouldQueue;
19+
use Illuminate\Foundation\Bus\Dispatchable;
20+
use Illuminate\Queue\InteractsWithQueue;
21+
use Illuminate\Queue\SerializesModels;
22+
use Illuminate\Support\Facades\Log;
23+
24+
/**
25+
* Class GenerateOTPRegistrationReminder
26+
* @package App\Jobs
27+
*/
28+
final class GenerateOTPRegistrationReminder implements ShouldQueue
29+
{
30+
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
31+
32+
private $user_id;
33+
34+
public function __construct(User $user)
35+
{
36+
$this->user_id = $user->getId();
37+
Log::debug(sprintf("GenerateOTPRegistrationReminder::GenerateOTPRegistrationReminder user %s", $user->getEmail()));
38+
}
39+
40+
/**
41+
* @param IAuthUserService $service
42+
* @return void
43+
* @throws \Exception
44+
*/
45+
public function handle(IAuthUserService $service)
46+
{
47+
Log::debug(sprintf("GenerateOTPRegistrationReminder::handle user %s", $this->user_id));
48+
$service->sendOTPRegistrationReminder($this->user_id);
49+
}
50+
51+
public function failed(\Throwable $exception)
52+
{
53+
Log::error($exception);
54+
}
55+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php namespace App\Mail;
2+
/**
3+
* Copyright 2024 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
15+
use Illuminate\Support\Facades\Config;
16+
use Illuminate\Support\Facades\Log;
17+
18+
/**
19+
* Class OTPRegistrationReminderEmail
20+
* @package App\Mail
21+
*/
22+
final class OTPRegistrationReminderEmail extends WelcomeNewUserEmail
23+
{
24+
/**
25+
* Build the message.
26+
*
27+
* @return $this
28+
*/
29+
public function build()
30+
{
31+
$this->subject = sprintf('[%1$s] Remember to set your password', Config::get('app.app_name'));
32+
$view = 'emails.oauth2_passwordless_otp_reg_reminder';
33+
34+
if (Config::get("app.tenant_name") == 'FNTECH') {
35+
$view = 'emails.oauth2_passwordless_otp_reg_reminder_fn';
36+
}
37+
38+
Log::debug(sprintf("OTPRegistrationReminderEmail::build to %s", $this->user_email));
39+
return $this->from(Config::get("mail.from"))
40+
->to($this->user_email)
41+
->subject($this->subject)
42+
->view($view);
43+
}
44+
}

app/Mail/WelcomeNewUserEmail.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Illuminate\Bus\Queueable;
1616
use Illuminate\Mail\Mailable;
1717
use Illuminate\Queue\SerializesModels;
18-
use Illuminate\Contracts\Queue\ShouldQueue;
1918
use Illuminate\Support\Facades\Config;
2019
use Illuminate\Support\Facades\Log;
2120
use Illuminate\Support\Facades\URL;
@@ -24,7 +23,7 @@
2423
* Class WelcomeNewUserEmail
2524
* @package App\Mail
2625
*/
27-
final class WelcomeNewUserEmail extends Mailable
26+
class WelcomeNewUserEmail extends Mailable
2827
{
2928
use Queueable, SerializesModels;
3029

@@ -101,8 +100,10 @@ public function __construct
101100
if($user->createdByOTP()){
102101
$this->user_created_by_otp = true;
103102
$otp = $user->getCreatedByOtp();
104-
$otp_redirect_url = $otp->getRedirectUrl();
105-
$this->site_base_url = !empty($otp_redirect_url) ? parse_url($otp_redirect_url)['host'] : null;
103+
if(!is_null($otp)) {
104+
$otp_redirect_url = $otp->getRedirectUrl();
105+
$this->site_base_url = !empty($otp_redirect_url) ? parse_url($otp_redirect_url)['host'] : null;
106+
}
106107
}
107108

108109
$this->reset_password_link = $reset_password_link;

app/Providers/EventServiceProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,15 @@ public function boot()
7575
Event::listen(UserEmailVerified::class, function($event)
7676
{
7777
$service = App::make(IUserService::class);
78-
if(is_null($service) || !$service instanceof IUserService) return;
78+
if(!$service instanceof IUserService) return;
7979
$service->sendSuccessfulVerificationEmail($event->getUserId());
8080
});
8181

8282
Event::listen(UserCreated::class, function($event)
8383
{
8484
// new user created
8585
$service = App::make(IUserService::class);
86-
if(is_null($service) || !$service instanceof IUserService) return;
86+
if(!$service instanceof IUserService) return;
8787
$service->initializeUser($event->getUserId());
8888
});
8989

app/Services/Auth/IUserService.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,11 @@ public function initializeUser(int $user_id):?User;
129129
* @throws \Exception
130130
*/
131131
public function updateRegistrationRequest(int $id, array $payload):UserRegistrationRequest;
132+
133+
/**
134+
* @param int $user_id
135+
* @return void
136+
* @throws \Exception
137+
*/
138+
public function sendOTPRegistrationReminder(int $user_id);
132139
}

app/Services/Auth/UserService.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use App\libs\Auth\Repositories\ISpamEstimatorFeedRepository;
2424
use App\libs\Auth\Repositories\IUserPasswordResetRequestRepository;
2525
use App\libs\Auth\Repositories\IUserRegistrationRequestRepository;
26+
use App\Mail\OTPRegistrationReminderEmail;
2627
use App\Mail\UserEmailVerificationRequest;
2728
use App\Mail\UserEmailVerificationSuccess;
2829
use App\Mail\UserPasswordResetRequestMail;
@@ -499,7 +500,7 @@ public function sendSuccessfulVerificationEmail(int $user_id): ?User
499500
return $this->tx_service->transaction(function() use($user_id){
500501

501502
$user = $this->user_repository->getById($user_id);
502-
if(is_null($user) || !$user instanceof User) return null;
503+
if(!$user instanceof User) return null;
503504

504505
$reset_password_link = null;
505506

@@ -527,7 +528,7 @@ public function initializeUser(int $user_id): ?User
527528
return $this->tx_service->transaction(function() use($user_id) {
528529
Log::debug(sprintf("UserService::initializeUser %s", $user_id));
529530
$user = $this->user_repository->getById($user_id);
530-
if(is_null($user) || !$user instanceof User) return null;
531+
if(!$user instanceof User) return null;
531532

532533
if(!$user->isEmailVerified()) {
533534
Log::debug(sprintf("UserService::initializeUser %s email not verified", $user_id));
@@ -560,4 +561,24 @@ public function initializeUser(int $user_id): ?User
560561
return $user;
561562
});
562563
}
564+
565+
/**
566+
* @param int $user_id
567+
* @return void
568+
* @throws \Exception
569+
*/
570+
public function sendOTPRegistrationReminder(int $user_id){
571+
$this->tx_service->transaction(function() use($user_id) {
572+
Log::debug(sprintf("UserService::sendOTPRegistrationReminder %s", $user_id));
573+
$user = $this->user_repository->getById($user_id);
574+
if( !$user instanceof User)
575+
throw new EntityNotFoundException(sprintf("User %s not found.", $user_id));
576+
577+
if ($user->hasPasswordSet())
578+
throw new ValidationException(sprintf("User %s already has password set.", $user->getId()));
579+
580+
$request = $this->generatePasswordResetRequest($user->getEmail());
581+
Mail::queue(new OTPRegistrationReminderEmail($user, $request->getResetLink()));
582+
});
583+
}
563584
}

app/libs/Auth/AuthService.php

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* limitations under the License.
1313
**/
1414

15+
use App\Jobs\GenerateOTPRegistrationReminder;
1516
use App\libs\OAuth2\Exceptions\ReloadSessionException;
1617
use App\libs\OAuth2\Repositories\IOAuth2OTPRepository;
1718
use App\Services\AbstractService;
@@ -206,7 +207,7 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
206207
)
207208
);
208209

209-
throw new AuthenticationException("Non existent OTP.");
210+
throw new AuthenticationException("Non existent single-use code.");
210211
}
211212

212213
$otp->logRedeemAttempt();
@@ -216,28 +217,28 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
216217
return $this->tx_service->transaction(function () use ($otp, $otpClaim, $client, $remember) {
217218

218219
if (!$otp->isAlive()) {
219-
throw new AuthenticationException("OTP is expired.");
220+
throw new AuthenticationException("Single-use code is expired.");
220221
}
221222

222223
if (!$otp->isValid()) {
223-
throw new AuthenticationException("OTP is not valid.");
224+
throw new AuthenticationException("Single-use code is not valid.");
224225
}
225226

226227
if ($otp->getValue() != $otpClaim->getValue()) {
227-
throw new AuthenticationException("OTP mismatch.");
228+
throw new AuthenticationException("Single-use code mismatch.");
228229
}
229230

230231
if(!empty($otpClaim->getScope()) && !$otp->allowScope($otpClaim->getScope()))
231-
throw new InvalidOTPException("OTP Requested scopes escalates former scopes.");
232+
throw new InvalidOTPException("Single-use code requested scopes escalates former scopes.");
232233

233234
if (($otp->hasClient() && is_null($client)) ||
234235
($otp->hasClient() && !is_null($client) && $client->getClientId() != $otp->getClient()->getClientId())) {
235-
throw new AuthenticationException("OTP audience mismatch.");
236+
throw new AuthenticationException("Single-use code audience mismatch.");
236237
}
237238

238239
$user = $this->getUserByUsername($otp->getUserName());
239-
240-
if (is_null($user)) {
240+
$new_user = is_null($user);
241+
if ($new_user) {
241242
// we need to create a new one ( auto register)
242243

243244
Log::debug(sprintf("AuthService::loginWithOTP user %s does not exists ...", $otp->getUserName()));
@@ -268,16 +269,20 @@ public function loginWithOTP(OAuth2OTP $otpClaim, ?Client $client = null, bool $
268269
foreach ($grants2Revoke as $otp2Revoke){
269270
try {
270271
Log::debug(sprintf("AuthService::loginWithOTP revoking otp %s ", $otp2Revoke->getValue()));
271-
if($otp2Revoke->getValue() !== $otpClaim->getValue())
272+
if ($otp2Revoke->getValue() !== $otpClaim->getValue())
272273
$otp2Revoke->redeem();
273-
}
274-
catch (Exception $ex){
274+
} catch (Exception $ex) {
275275
Log::warning($ex);
276276
}
277277
}
278278

279279
Auth::login($user, $remember);
280280

281+
if (!$user->hasPasswordSet() && !$new_user) {
282+
// trigger background job
283+
GenerateOTPRegistrationReminder::dispatch($user);
284+
}
285+
281286
return $otp;
282287
});
283288
}

0 commit comments

Comments
 (0)