-
Notifications
You must be signed in to change notification settings - Fork 1k
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
base: main
Are you sure you want to change the base?
Changes from 10 commits
1ee817f
0f4e2b9
045819c
57a761f
d5c63a6
5a447da
c86eaf3
17e30fe
f7061de
372e36f
a72fd8d
2c20589
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -709,7 +709,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 ("") | ||
|
@@ -910,10 +914,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` | ||
|
||
Here is an example of what it should look like in the configuration: | ||
|
@@ -973,7 +977,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'); | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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','/^[A-Za-z0-9]+$/'); // specify regex pattern for username, defaults to alphanumeric characters | ||
$usernameMinLength = (int)$this->getProperty('usernameMinLength',5); | ||
$usernameMaxLength = (int)$this->getProperty('usernameMaxLength',30); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel 30 is too low for an email address for instance. I would default to 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', ''); | ||
|
@@ -95,22 +104,59 @@ 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] = filter_var($value, FILTER_VALIDATE_EMAIL) ? $value : filter_var($value,FILTER_SANITIZE_ENCODED); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This breaks internationalization (Chinese characters for instance). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure yet how to resolve this. Any issue if we simply just |
||
//sanitize all other inputs, except for valid or properly formatted email address | ||
} | ||
} | ||
} | ||
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()); | ||
apps-caraga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
$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 { | ||
|
@@ -128,6 +174,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); | ||
} | ||
|
@@ -176,6 +223,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, ''); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this should default to non-white-space printable characters in any language (Chinese characters included).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this be sufficient as the regex pattern? Haven't done much checking with other languages,but basically, we'll be checking if it's alphanumeric and also if it's a printable char in Unicode.
'/^[[:alnum:][:print:]]+$/u'