Skip to content

Commit bd69e74

Browse files
authored
Merge pull request #104 from ellaisys/hotfix
Hotfix
2 parents 5d33b7c + 1a4c2ea commit bd69e74

18 files changed

+798
-476
lines changed

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Build
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
types: [opened, synchronize, reopened]
8+
jobs:
9+
sonarcloud:
10+
name: SonarCloud
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
with:
15+
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
16+
- name: SonarCloud Scan
17+
uses: SonarSource/sonarcloud-github-action@master
18+
env:
19+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
20+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
Release 40 (tag v1.3.0)
2+
- Feat: Issue #50, Architecture change to map the local and cognito users with sub (SubjectId)
3+
- Fix: Issue #86, SSO enabled the user is now created for both guards
4+
- Fix: Code optimization
5+
16
Release 39 (tag v1.2.5)
27
- Fix: AWS JWT Token validation timeout
38
- Fix: Non declared variable references

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ AWS Cognito package using the AWS SDK for PHP
1212
[![GitHub Contributors](https://img.shields.io/github/contributors-anon/ellaisys/aws-cognito?style=flat&logo=github&logoColor=whitesmoke&label=Contributors)](CONTRIBUTING.md) 
1313
[![APM](https://img.shields.io/packagist/l/ellaisys/aws-cognito?style=flat-square&logo=github&logoColor=whitesmoke&label=License)](LICENSE.md)
1414

15+
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ellaisys_aws-cognito&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=ellaisys_aws-cognito)
16+
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ellaisys_aws-cognito&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=ellaisys_aws-cognito)
17+
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ellaisys_aws-cognito&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ellaisys_aws-cognito)
18+
19+
1520
This package provides a simple way to use AWS Cognito authentication in Laravel for Web and API Auth Drivers.
1621
The idea of this package, and some of the code, is based on the package from Pod-Point which you can find here: [Pod-Point/laravel-cognito-auth](https://github.com/Pod-Point/laravel-cognito-auth), [black-bits/laravel-cognito-auth](https://github.com/black-bits/laravel-cognito-auth) and [tymondesigns/jwt-auth](https://github.com/tymondesigns/jwt-auth).
1722

@@ -20,12 +25,12 @@ The idea of this package, and some of the code, is based on the package from Pod
2025
We decided to use it and contribute it to the community as a package, that encourages standarised use and a RAD tool for authentication using AWS Cognito.
2126

2227
## Features
23-
- [Registration and Confirmation E-Mail (Sign Up)](#registering-users) **Updated** (#9 feature added)
28+
- [Registration and Confirmation E-Mail (Sign Up)](#registering-users)
2429
- Forced password change at first login (configurable)
2530
- [Login (Sign In)](#user-authentication)
26-
- Token Validation for all Session and Token Guard Requests **New**
31+
- Token Validation for all Session and Token Guard Requests
2732
- Remember Me Cookie
28-
- Single Sign On
33+
- Single Sign On **Updated** (Fix: Issue #86)
2934
- Forgot Password (Resend - configurable)
3035
- User Deletion
3136
- Edit User Attributes
@@ -41,6 +46,7 @@ We decided to use it and contribute it to the community as a package, that encou
4146
- [Forced Logout (Sign Out) - Revoke the RefreshToken from AWS](#signout-remove-access-token)
4247
- [MFA Implementation for Session and Token Guards](./README_MFA.md)
4348
- [Password validation based on Cognito Configuration](#password-validation-based-of-cognito-configuration)
49+
- [Mapping Cognito User using Subject UUID](#mapping-cognito-user-using-subject-uuid) **NEW**
4450

4551
## Compatability
4652

@@ -197,7 +203,6 @@ At the current state you need to have those 4 form fields defined in here. Those
197203
With our package and AWS Cognito we provide you a simple way to use Single Sign-Ons.
198204
For configuration options take a look at the config [cognito.php](/config/cognito.php).
199205

200-
201206
When you want SSO enabled and a user tries to login into your application, the package checks if the user exists in your AWS Cognito pool. If the user exists, he will be created automatically in your database provided the `add_missing_local_user` is to `true`, and is logged in simultaneously.
202207

203208
That's what we use the fields `sso_user_model` and `cognito_user_fields` for. In `sso_user_model` you define the class of your user model. In most cases this will simply be _App\Models\User_.
@@ -626,6 +631,20 @@ This library fetches the password policy from the cognito pool configurations. T
626631
>[!IMPORTANT]
627632
>In case of special characters, we are supporting all except the pipe character **|** for now.
628633
634+
## Mapping Cognito User using Subject UUID
635+
636+
The library maps the Cognito user subject UUID with the local repository. Everytime a new user is created in cognito, the sub UUID is mapped with the local user table with an user specified column name.
637+
638+
The column in the local BD is identified with the config parameter `user_subject_uuid` with the default value set to `sub`.
639+
640+
However, to customize the column name in the local DB user table, you may do that with below setting fields to your `.env` file
641+
642+
```php
643+
644+
AWS_COGNITO_USER_SUBJECT_UUID="sub"
645+
646+
```
647+
629648
We are working on making sure that pipe character is handled soon.
630649

631650
## Changelog

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ellaisys/aws-cognito",
33
"description": "AWS Cognito package that allows Auth and other related features using the AWS SDK for PHP",
4-
"keywords": ["php", "laravel", "aws", "cognito", "auth", "authentication", "oauth", "user pool", "ellaisys"],
4+
"keywords": ["php", "laravel", "aws", "cognito", "auth", "authentication", "oauth", "user pool", "ellaisys", "mfa", "multi-factor authentication", "2fa", "two-factor authentication", "password", "reset", "forgot", "change", "update", "email", "phone", "sms", "email verification", "phone verification", "sms verification", "email confirmation", "phone confirmation", "sms confirmation", "email code", "phone code", "sms code", "email token", "phone token", "sms token", "email password", "phone password", "sms password", "email recovery", "phone recovery", "sms recovery", "email reset", "phone reset", "sms reset", "email forgot", "phone forgot", "sms forgot", "email change", "phone change", "sms change", "email update", "phone update", "sms update", "email verify", "phone verify", "sms verify", "email confirm", "phone confirm", "sms confirm", "email code verification", "phone code verification", "sms code verification", "email token verification", "phone token verification", "sms token verification", "email password reset", "phone password reset", "sms password reset", "email recovery password", "phone recovery password", "sms recovery password", "email reset password", "phone reset password", "sms reset password", "email forgot password", "phone forgot password", "sms forgot password", "email change password", "phone change password", "sms change password", "email update password", "phone update password", "sms update password", "email verify code", "phone verify code", "sms verify code", "email confirm code", "phone confirm code", "sms confirm code", "email code verify", "phone code verify", "sms code verify", "email token verify", "phone token verify", "sms token verify", "email password reset code", "phone password reset code", "sms password reset code", "email recovery password code", "phone recovery password code", "sms recovery password code", "email reset password code", "phone reset password code", "sms reset password code", "email forgot password code", "phone forgot password code", "sms forgot password code", "email change password code", "phone change password code", "sms change password code", "email update password code", "phone update password code", "sms update password code", "email verify code verification", "phone verify code verification", "sms verify code verification", "email confirm code verification", "phone confirm code verification", "sms confirm code verification", "email code verify verification", "phone code verify verification", "sms"],
55
"type": "library",
66
"license": "MIT",
77
"homepage": "https://ellaisys.github.io/aws-cognito/",

config/cognito.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
| This option controls the default cognito fields that shall be needed to be
5656
| updated. The array value is a mapping with DB model or Request data.
5757
|
58+
| DO NOT change the parameters on the left side of the array. They map to
59+
| the AWS Cognito User Pool fields.
60+
|
61+
| The right side of the array is the DB model field, and you can set the
62+
| value to null if you do not want to update the field.
63+
|
5864
*/
5965
'cognito_user_fields' => [
6066
'name' => 'name',
@@ -64,12 +70,26 @@
6470
'nickname' => null,
6571
'preferred_username' => null,
6672
'email' => 'email', //Do Not set this parameter to null
67-
'phone_number' => 'phone',
73+
'phone_number' => null,
6874
'gender' => null,
6975
'birthdate' => null,
7076
'locale' => null
7177
],
7278

79+
80+
/*
81+
|--------------------------------------------------------------------------
82+
| Cognito Subject UUID
83+
|--------------------------------------------------------------------------
84+
|
85+
| This option controls the default cognito subject UUID that shall be needed
86+
| to be updated based on your local DB schema. This value is the attribute
87+
| in the local DB Model that maps with Cognito user subject UUID.
88+
|
89+
*/
90+
'user_subject_uuid' => env('AWS_COGNITO_USER_SUBJECT_UUID', 'sub'),
91+
92+
7393
/*
7494
|--------------------------------------------------------------------------
7595
| Cognito New User
@@ -104,7 +124,7 @@
104124
|--------------------------------------------------------------------------
105125
|
106126
| This option controls the cognito MFA configuration for the assigned user.
107-
|
127+
|
108128
|
109129
| MFA_NONE, MFA_ENABLED
110130
|
@@ -137,7 +157,7 @@
137157
|
138158
*/
139159
'add_missing_local_user' => env('AWS_COGNITO_ADD_LOCAL_USER', false),
140-
'delete_user' => env('AWS_COGNITO_DELETE_USER', false),
160+
'delete_user' => env('AWS_COGNITO_DELETE_USER', false),
141161

142162
// Package configurations
143163
'sso_user_model' => env('AWS_COGNITO_USER_MODEL', 'App\Models\User'),

sonar-project.properties

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
sonar.projectKey=ellaisys_aws-cognito
2+
sonar.organization=ellaisys
3+
4+
# This is the name and version displayed in the SonarCloud UI.
5+
sonar.projectName=aws-cognito
6+
sonar.projectVersion=1.3.0
7+
8+
9+
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
10+
#sonar.sources=.
11+
12+
# Encoding of the source code. Default is default system encoding
13+
sonar.sourceEncoding=UTF-8

src/Auth/AuthenticatesUsers.php

Lines changed: 36 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ protected function getAdminListGroupsForUser(string $username)
5353
foreach ($groups as $key => &$value) {
5454
unset($value['UserPoolId']);
5555
unset($value['RoleArn']);
56-
} //Loop ends
56+
} //Loop ends
5757
} //End if
5858
} //End if
5959
} catch(Exception $e) {
@@ -75,9 +75,16 @@ protected function getAdminListGroupsForUser(string $username)
7575
*
7676
* @return mixed
7777
*/
78-
protected function attemptLogin(Collection $request, string $guard='web', string $paramUsername='email', string $paramPassword='password', bool $isJsonResponse=false)
78+
protected function attemptLogin(Request|Collection $request, string $guard='web', string $paramUsername='email', string $paramPassword='password', bool $isJsonResponse=false)
7979
{
8080
try {
81+
$returnValue = null;
82+
83+
//Convert request to collection
84+
if ($request instanceof Request) {
85+
$request = collect($request->all());
86+
} //End if
87+
8188
//Get the password policy
8289
$passwordPolicy = app()->make(AwsCognitoUserPool::class)->getPasswordPolicy(true);
8390

@@ -88,44 +95,34 @@ protected function attemptLogin(Collection $request, string $guard='web', string
8895
'regex' => 'Must contain atleast ' . $passwordPolicy['message']
8996
]);
9097
if ($validator->fails()) {
91-
Log::info($validator->errors());
98+
Log::error($validator->errors());
9299
throw new ValidationException($validator);
93100
} //End if
94101

95-
//Get the configuration fields
96-
$userFields = config('cognito.cognito_user_fields');
97-
98-
//Get key fields
99-
$keyUsername = $userFields['email'];
100-
$keyPassword = 'password';
101-
$rememberMe = $request->has('remember')?$request['remember']:false;
102-
103-
//Generate credentials array
104-
$credentials = [
105-
$keyUsername => $request[$paramUsername],
106-
$keyPassword => $request[$paramPassword]
107-
];
108-
109102
//Authenticate User
110-
$claim = Auth::guard($guard)->attempt($credentials, $rememberMe);
103+
$returnValue = Auth::guard($guard)->attempt($request->toArray(), false, $paramUsername, $paramPassword);
104+
} catch (NoLocalUserException | CognitoIdentityProviderException | Exception $e) {
105+
$exceptionClass = basename(str_replace('\\', DIRECTORY_SEPARATOR, get_class($e)));
106+
$exceptionCode = $e->getCode();
107+
$exceptionMessage = $e->getMessage().':(code:'.$exceptionCode.', line:'.$e->getLine().')';
108+
if ($e instanceof CognitoIdentityProviderException) {
109+
$exceptionCode = $e->getAwsErrorCode();
110+
$exceptionMessage = $e->getAwsErrorMessage().':'.$exceptionCode;
111+
} //End if
112+
Log::error('AuthenticatesUsers:attemptLogin:'.$exceptionClass.':'.$exceptionMessage);
111113

112-
} catch (NoLocalUserException $e) {
113-
Log::error('AuthenticatesUsers:attemptLogin:NoLocalUserException');
114-
$user = $this->createLocalUser($credentials, $keyPassword);
115-
if ($user) {
116-
return $user;
114+
if ($e instanceof ValidationException) {
115+
throw $e;
117116
} //End if
118117

119-
return $this->sendFailedLoginResponse($request, $e, $isJsonResponse, $paramUsername);
120-
} catch (CognitoIdentityProviderException $e) {
121-
Log::error('AuthenticatesUsers:attemptLogin:CognitoIdentityProviderException');
122-
return $this->sendFailedCognitoResponse($e, $isJsonResponse, $paramUsername);
123-
} catch (Exception $e) {
124-
Log::error('AuthenticatesUsers:attemptLogin:Exception');
125-
return $this->sendFailedLoginResponse($request, $e, $isJsonResponse, $paramUsername);
118+
if ($e instanceof CognitoIdentityProviderException) {
119+
$this->sendFailedCognitoResponse($e, $isJsonResponse, $paramUsername);
120+
}
121+
122+
$returnValue = $this->sendFailedLoginResponse($request, $e, $isJsonResponse, $paramUsername);
126123
} //Try-catch ends
127124

128-
return $claim;
125+
return $returnValue;
129126
} //Function ends
130127

131128

@@ -156,15 +153,13 @@ protected function attemptLoginMFA($request, string $guard='web', bool $isJsonRe
156153
$challenge = $request->only(['challenge_name', 'session', 'mfa_code'])->toArray();
157154

158155
//Fetch user details
159-
$user = null;
160156
switch ($guard) {
161157
case 'web': //Web
162158
if (request()->session()->has($challenge['session'])) {
163159
//Get stored session
164160
$sessionToken = request()->session()->get($challenge['session']);
165161
$username = $sessionToken['username'];
166162
$challenge['username'] = $username;
167-
$user = unserialize($sessionToken['user']);
168163
} else{
169164
throw new HttpException(400, 'ERROR_AWS_COGNITO_SESSION_MFA_CODE');
170165
} //End if
@@ -174,29 +169,20 @@ protected function attemptLoginMFA($request, string $guard='web', bool $isJsonRe
174169
$challengeData = Auth::guard($guard)->getChallengeData($challenge['session']);
175170
$username = $challengeData['username'];
176171
$challenge['username'] = $username;
177-
$user = unserialize($challengeData['user']);
178172
break;
179173

180174
default:
181-
$user = null;
182175
break;
183176
} //End switch
184177

185178
//Authenticate User
186-
$claim = Auth::guard($guard)->attemptMFA($challenge, $user);
179+
$claim = Auth::guard($guard)->attemptMFA($challenge);
187180
} catch (NoLocalUserException $e) {
188181
Log::error('AuthenticatesUsers:attemptLoginMFA:NoLocalUserException');
189-
190-
$response = $this->createLocalUser($user->toArray());
191-
if ($response) {
192-
return $response;
193-
} //End if
194-
195182
return $this->sendFailedLoginResponse($request, $e, $isJsonResponse, $paramUsername);
196183
} catch (CognitoIdentityProviderException $e) {
197184
Log::error('AuthenticatesUsers:attemptLoginMFA:CognitoIdentityProviderException');
198185
return $this->sendFailedLoginResponse($request, $e, $isJsonResponse, $paramName);
199-
200186
} catch (Exception $e) {
201187
Log::error('AuthenticatesUsers:attemptLoginMFA:Exception');
202188
Log::error($e);
@@ -216,32 +202,6 @@ protected function attemptLoginMFA($request, string $guard='web', bool $isJsonRe
216202
} //Function ends
217203

218204

219-
/**
220-
* Create a local user if one does not exist.
221-
*
222-
* @param array $credentials
223-
* @return mixed
224-
*/
225-
protected function createLocalUser(array $dataUser, string $keyPassword='password')
226-
{
227-
$user = null;
228-
if (config('cognito.add_missing_local_user')) {
229-
//Get user model from configuration
230-
$userModel = config('cognito.sso_user_model');
231-
232-
//Remove password from credentials if exists
233-
if (array_key_exists($keyPassword, $dataUser)) {
234-
unset($dataUser[$keyPassword]);
235-
} //End if
236-
237-
//Create user
238-
$user = $userModel::create($dataUser);
239-
} //End if
240-
241-
return $user;
242-
} //Function ends
243-
244-
245205
/**
246206
* Handle Failed Cognito Exception
247207
*
@@ -263,33 +223,34 @@ private function sendFailedCognitoResponse(CognitoIdentityProviderException $exc
263223
*/
264224
private function sendFailedLoginResponse($request, $exception=null, bool $isJsonResponse=false, string $paramName='email')
265225
{
266-
$errorCode = 'cognito.validation.auth.failed';
226+
$errorCode = 400;
227+
$errorMessageCode = 'cognito.validation.auth.failed';
267228
$message = 'FailedLoginResponse';
268229
if (!empty($exception)) {
269230
if ($exception instanceof CognitoIdentityProviderException) {
270-
$errorCode = $exception->getAwsErrorCode();
231+
$errorMessageCode = $exception->getAwsErrorCode();
271232
$message = $exception->getAwsErrorMessage();
272233
} elseif ($exception instanceof ValidationException) {
273234
throw $exception;
274235
} else {
236+
$errorCode = $exception->getStatusCode();
275237
$message = $exception->getMessage();
276238
} //End if
277239
} //End if
278240

279241
if ($isJsonResponse) {
280242
return response()->json([
281-
'error' => $errorCode,
243+
'error' => $errorMessageCode,
282244
'message' => $message
283-
], 400);
245+
], $errorCode);
284246
} else {
285247
return redirect()
286248
->back()
287249
->withErrors([
250+
'error' => $errorMessageCode,
288251
$paramName => $message,
289252
]);
290253
} //End if
291-
292-
throw new HttpException(400, $message);
293254
} //Function ends
294255

295256

0 commit comments

Comments
 (0)