Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process add'l data on registration #911

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,11 @@ You can tune the middleware behavior using middleware specific configuration par
- "dbAuth.usernameColumn": The users table column that holds usernames ("username")
- "dbAuth.passwordColumn": The users table column that holds passwords ("password")
- "dbAuth.returnedColumns": The columns returned on successful login, empty means 'all' ("")
- "dbAuth.refreshSession": Number of minutes before a session is refreshed via api.php/me endpoint, (0)
- "dbAuth.usernameFormField": The name of the form field that holds the username ("username")
- "dbAuth.usernamePattern": Specify regex pattern for username. Defaults to alpha-numeric charactes ("/^[A-Za-z0-9]+$/")
- "dbAuth.usernameMaxLength": Specify maximum length of username (30)
- "dbAuth.usernameMinLength": Specify minimum length of username (5)
- "dbAuth.passwordFormField": The name of the form field that holds the password ("password")
- "dbAuth.newPasswordFormField": The name of the form field that holds the new password ("newPassword")
- "dbAuth.registerUser": JSON user data (or "1") in case you want the /register endpoint enabled ("")
Expand Down Expand Up @@ -939,10 +943,10 @@ Add a web application to this project and grab the code snippet for later use.
Then you have to configure the `jwtAuth.secrets` configuration in your `api.php` file.
This can be done as follows:

a. Log a user in to your Firebase-based app, get an authentication token for that user
b. Go to [https://jwt.io/](https://jwt.io/) and paste the token in the decoding field
c. Read the decoded header information from the token, it will give you the correct `kid`
d. Grab the public key via this [URL](https://www.googleapis.com/robot/v1/metadata/x509/[email protected]), which corresponds to your `kid` from previous step
a. Log a user in to your Firebase-based app, get an authentication token for that user
b. Go to [https://jwt.io/](https://jwt.io/) and paste the token in the decoding field
c. Read the decoded header information from the token, it will give you the correct `kid`
d. Grab the public key via this [URL](https://www.googleapis.com/robot/v1/metadata/x509/[email protected]), which corresponds to your `kid` from previous step
e. Now, just fill `jwtAuth.secrets` with your public key in the `api.php`

Also configure the `jwtAuth.audiences` (fill in the Firebase project ID).
Expand Down Expand Up @@ -1006,7 +1010,7 @@ and define a 'authorization.tableHandler' function that returns 'false' for thes
},

The above example will restrict access to the table 'license_keys' for all operations.

'authorization.columnHandler' => function ($operation, $tableName, $columnName) {
return !($tableName == 'users' && $columnName == 'password');
},
Expand Down
73 changes: 69 additions & 4 deletions src/Tqdev/PhpCrudApi/Middleware/DbAuthMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$usernameColumnName = $this->getProperty('usernameColumn', 'username');
$usernameColumn = $table->getColumn($usernameColumnName);
$passwordColumnName = $this->getProperty('passwordColumn', 'password');
$usernamePattern = $this->getProperty('usernamePattern','/^\p{L}+$/u'); // specify regex pattern for username, defaults to printable chars only,no punctation or numbers,unicode mode
$usernameMinLength = (int)$this->getProperty('usernameMinLength',5);
$usernameMaxLength = (int)$this->getProperty('usernameMaxLength',255);
if($usernameMinLength > $usernameMaxLength){
//obviously, $usernameMinLength should be less than $usernameMaxLength, but we'll still check in case of mis-config then we'll swap the 2 values
$lesser = $usernameMaxLength;
$usernameMaxLength = $usernameMinLength;
$usernameMinLength = $lesser;
}
$passwordLength = $this->getProperty('passwordLength', '12');
$pkName = $table->getPk()->getName();
$registerUser = $this->getProperty('registerUser', '');
Expand All @@ -95,22 +104,58 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
if (strlen($password) < $passwordLength) {
return $this->responder->error(ErrorCode::PASSWORD_TOO_SHORT, $passwordLength);
}
if(strlen($username) < $usernameMinLength){
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $username . " [ Username length must be at least ". $usernameMinLength ." characters.]");
}
if(strlen($username) > $usernameMaxLength){
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $username . " [ Username length must not exceed ". $usernameMaxLength ." characters.]");
}
if(!preg_match($usernamePattern, $username)){
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $username . " [ Username contains disallowed characters.]");
}
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
if (!empty($users)) {
return $this->responder->error(ErrorCode::USER_ALREADY_EXIST, $username);
}
$data = json_decode($registerUser, true);
$data = is_array($data) ? $data : [];
$data[$usernameColumnName] = $username;
$data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
$this->db->createSingle($table, $data);
$data = is_array($data) ? $data : (array)$body;
// get the original posted data
$userTableColumns = $table->getColumnNames();
foreach($data as $key=>$value){
if(in_array($key,$userTableColumns)){
// process only posted data if the key exists as users table column
if($key === $usernameColumnName){
$data[$usernameColumnName] = $username; //process the username and password as usual
}else if($key === $passwordColumnName){
$data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
}else{
$data[$key] = htmlspecialchars($value);
}
}
}
try{
$this->db->createSingle($table, $data);
/* Since we're processing additional data during registration, we need to check if these data were defined in db to be unique.
* For example, emailAddress are usually used just once in an application. We can query the database to check if the new emailAddress is not yet registered,
* but, in some cases, we may more than 2 or 3 or more unique fields (not common, but possible), hence we would also need to
* query 2,3 or more times.
* As a TEMPORARY WORKAROUND, we'll just attempt to register the new user and wait for the db to throw a DUPLICATE KEY EXCEPTION.
*/
}catch(\PDOException $error){
if($error->getCode() ==="23000"){
return $this->responder->error(ErrorCode::DUPLICATE_KEY_EXCEPTION,'',$error->getMessage());
}else{
return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED,$error->getMessage());
}
}
$users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
foreach ($users as $user) {
if ($loginAfterRegistration) {
if (!headers_sent()) {
session_regenerate_id(true);
}
unset($user[$passwordColumnName]);
$_SESSION['updatedAt'] = time();
$_SESSION['user'] = $user;
return $this->responder->success($user);
} else {
Expand All @@ -128,6 +173,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
session_regenerate_id(true);
}
unset($user[$passwordColumnName]);
$_SESSION['updatedAt'] = time();
$_SESSION['user'] = $user;
return $this->responder->success($user);
}
Expand Down Expand Up @@ -176,6 +222,25 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}
if ($method == 'GET' && $path == 'me') {
if (isset($_SESSION['user'])) {
$updateAfter = $this->getProperty('refreshSession',0) * 60;//update session after x minutes
if($updateAfter > 0 &&( time() >($_SESSION['user']['updatedAt'] + $updateAfter))){
$tableName = $this->getProperty('loginTable','users');
$table = $this->reflection->getTable($tableName);
$pkName = $table->getPk()->getName();
$passwordColumnName = $this->getProperty('passwordColumn','');
$returnedColumns = $this->getProperty('returnedColumns','');
if(!$returnedColumns){
$columnNames = $table->getColumnNames();
}else{
$columnNames = array_map('trim',explode(',',$returnedColumns));
$columnNames[] = $passwordColumnName;
$columnNames = array_values(array_unique($columnNames));
}
$user = $this->db->selectSingle($table,$columnNames,$_SESSION['user'][$pkName]);
unset($user[$passwordColumnName]);
$user['updatedAt'] = time();
$_SESSION['user'] = $user;
}
return $this->responder->success($_SESSION['user']);
}
return $this->responder->error(ErrorCode::AUTHENTICATION_REQUIRED, '');
Expand Down