mirror of
https://github.com/delight-im/PHP-Auth.git
synced 2025-08-08 09:06:29 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
095b8ccc70 | ||
|
550a6d0355 | ||
|
c494e0fa13 | ||
|
d7d9899167 | ||
|
05165a44a6 | ||
|
c3f2097750 | ||
|
395a065fd4 | ||
|
627c592891 | ||
|
2a6d1c4f7d | ||
|
a63e5ec053 | ||
|
4115340927 | ||
|
09dac6a5f5 | ||
|
3a7a860c6d | ||
|
131aea3ded | ||
|
e14f3d1925 | ||
|
1d54ff2f6b | ||
|
ec6afdad48 | ||
|
58e69fdd0e | ||
|
e7e174b05d | ||
|
8f35cc9965 |
@@ -25,9 +25,7 @@ CREATE TABLE IF NOT EXISTS "users_confirmations" (
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "email_expires" ON "users_confirmations" ("email", "expires");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "user_id" ON "users_confirmations" ("user_id");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users_remembered" (
|
||||
@@ -37,7 +35,6 @@ CREATE TABLE IF NOT EXISTS "users_remembered" (
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "user" ON "users_remembered" ("user");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users_resets" (
|
||||
@@ -47,7 +44,6 @@ CREATE TABLE IF NOT EXISTS "users_resets" (
|
||||
"token" VARCHAR(255) NOT NULL,
|
||||
"expires" INTEGER NOT NULL CHECK ("expires" >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "user_expires" ON "users_resets" ("user", "expires");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users_throttling" (
|
||||
@@ -56,7 +52,6 @@ CREATE TABLE IF NOT EXISTS "users_throttling" (
|
||||
"replenished_at" INTEGER NOT NULL CHECK ("replenished_at" >= 0),
|
||||
"expires_at" INTEGER NOT NULL CHECK ("expires_at" >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "expires_at" ON "users_throttling" ("expires_at");
|
||||
|
||||
COMMIT;
|
||||
|
30
README.md
30
README.md
@@ -82,6 +82,7 @@ Migrating from an earlier version of this project? See our [upgrade guide](Migra
|
||||
* [Taking roles away from users](#taking-roles-away-from-users)
|
||||
* [Checking roles](#checking-roles-1)
|
||||
* [Impersonating users (logging in as user)](#impersonating-users-logging-in-as-user)
|
||||
* [Changing a user’s password](#changing-a-users-password)
|
||||
* [Cookies](#cookies)
|
||||
* [Renaming the library’s cookies](#renaming-the-librarys-cookies)
|
||||
* [Defining the domain scope for cookies](#defining-the-domain-scope-for-cookies)
|
||||
@@ -1001,6 +1002,35 @@ catch (\Delight\Auth\EmailNotVerifiedException $e) {
|
||||
}
|
||||
```
|
||||
|
||||
#### Changing a user’s password
|
||||
|
||||
```php
|
||||
try {
|
||||
$auth->admin()->changePasswordForUserById($_POST['id'], $_POST['newPassword']);
|
||||
}
|
||||
catch (\Delight\Auth\UnknownIdException $e) {
|
||||
// unknown ID
|
||||
}
|
||||
catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
// invalid password
|
||||
}
|
||||
|
||||
// or
|
||||
|
||||
try {
|
||||
$auth->admin()->changePasswordForUserByUsername($_POST['username'], $_POST['newPassword']);
|
||||
}
|
||||
catch (\Delight\Auth\UnknownUsernameException $e) {
|
||||
// unknown username
|
||||
}
|
||||
catch (\Delight\Auth\AmbiguousUsernameException $e) {
|
||||
// ambiguous username
|
||||
}
|
||||
catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
// invalid password
|
||||
}
|
||||
```
|
||||
|
||||
### Cookies
|
||||
|
||||
This library uses two cookies to keep state on the client: The first, whose name you can retrieve using
|
||||
|
@@ -9,6 +9,7 @@
|
||||
namespace Delight\Auth;
|
||||
|
||||
use Delight\Db\PdoDatabase;
|
||||
use Delight\Db\PdoDsn;
|
||||
use Delight\Db\Throwable\Error;
|
||||
|
||||
require_once __DIR__ . '/Exceptions.php';
|
||||
@@ -17,12 +18,10 @@ require_once __DIR__ . '/Exceptions.php';
|
||||
final class Administration extends UserManager {
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @param PdoDatabase $databaseConnection the database connection to operate on
|
||||
* @param PdoDatabase|PdoDsn|\PDO $databaseConnection the database connection to operate on
|
||||
* @param string|null $dbTablePrefix (optional) the prefix for the names of all database tables used by this component
|
||||
*/
|
||||
public function __construct(PdoDatabase $databaseConnection, $dbTablePrefix = null) {
|
||||
public function __construct($databaseConnection, $dbTablePrefix = null) {
|
||||
parent::__construct($databaseConnection, $dbTablePrefix);
|
||||
}
|
||||
|
||||
@@ -370,6 +369,49 @@ final class Administration extends UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the password for the user with the given ID
|
||||
*
|
||||
* @param int $userId the ID of the user whose password to change
|
||||
* @param string $newPassword the new password to set
|
||||
* @throws UnknownIdException if no user with the specified ID has been found
|
||||
* @throws InvalidPasswordException if the desired new password has been invalid
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
public function changePasswordForUserById($userId, $newPassword) {
|
||||
$userId = (int) $userId;
|
||||
$newPassword = self::validatePassword($newPassword);
|
||||
|
||||
$this->updatePasswordInternal(
|
||||
$userId,
|
||||
$newPassword
|
||||
);
|
||||
|
||||
$this->deleteRememberDirectiveForUserById($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the password for the user with the given username
|
||||
*
|
||||
* @param string $username the username of the user whose password to change
|
||||
* @param string $newPassword the new password to set
|
||||
* @throws UnknownUsernameException if no user with the specified username has been found
|
||||
* @throws AmbiguousUsernameException if multiple users with the specified username have been found
|
||||
* @throws InvalidPasswordException if the desired new password has been invalid
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
public function changePasswordForUserByUsername($username, $newPassword) {
|
||||
$userData = $this->getUserDataByUsername(
|
||||
\trim($username),
|
||||
[ 'id' ]
|
||||
);
|
||||
|
||||
$this->changePasswordForUserById(
|
||||
(int) $userData['id'],
|
||||
$newPassword
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all existing users where the column with the specified name has the given value
|
||||
*
|
||||
@@ -510,7 +552,7 @@ final class Administration extends UserManager {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
|
||||
$numberOfMatchingUsers = \count($users);
|
||||
$numberOfMatchingUsers = ($users !== null) ? \count($users) : 0;
|
||||
|
||||
if ($numberOfMatchingUsers === 1) {
|
||||
$user = $users[0];
|
||||
|
74
src/Auth.php
74
src/Auth.php
@@ -48,7 +48,7 @@ final class Auth extends UserManager {
|
||||
$this->sessionResyncInterval = isset($sessionResyncInterval) ? ((int) $sessionResyncInterval) : (60 * 5);
|
||||
$this->rememberCookieName = self::createRememberCookieName();
|
||||
|
||||
$this->initSession();
|
||||
$this->initSessionIfNecessary();
|
||||
$this->enhanceHttpSecurity();
|
||||
|
||||
$this->processRememberDirective();
|
||||
@@ -56,16 +56,18 @@ final class Auth extends UserManager {
|
||||
}
|
||||
|
||||
/** Initializes the session and sets the correct configuration */
|
||||
private function initSession() {
|
||||
// use cookies to store session IDs
|
||||
\ini_set('session.use_cookies', 1);
|
||||
// use cookies only (do not send session IDs in URLs)
|
||||
\ini_set('session.use_only_cookies', 1);
|
||||
// do not send session IDs in URLs
|
||||
\ini_set('session.use_trans_sid', 0);
|
||||
private function initSessionIfNecessary() {
|
||||
if (\session_status() === \PHP_SESSION_NONE) {
|
||||
// use cookies to store session IDs
|
||||
\ini_set('session.use_cookies', 1);
|
||||
// use cookies only (do not send session IDs in URLs)
|
||||
\ini_set('session.use_only_cookies', 1);
|
||||
// do not send session IDs in URLs
|
||||
\ini_set('session.use_trans_sid', 0);
|
||||
|
||||
// start the session (requests a cookie to be written on the client)
|
||||
@Session::start();
|
||||
// start the session (requests a cookie to be written on the client)
|
||||
@Session::start();
|
||||
}
|
||||
}
|
||||
|
||||
/** Improves the application's security over HTTP(S) by setting specific headers */
|
||||
@@ -377,7 +379,7 @@ final class Auth extends UserManager {
|
||||
// if a user ID was set
|
||||
if (isset($userId)) {
|
||||
// delete any existing remember directives
|
||||
$this->deleteRememberDirective($userId);
|
||||
$this->deleteRememberDirectiveForUserById($userId);
|
||||
}
|
||||
|
||||
// remove all session variables maintained by this library
|
||||
@@ -437,22 +439,8 @@ final class Auth extends UserManager {
|
||||
$this->setRememberCookie($selector, $token, $expires);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an existing directive that keeps the user logged in ("remember me")
|
||||
*
|
||||
* @param int $userId the user ID that shouldn't be kept signed in anymore
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
private function deleteRememberDirective($userId) {
|
||||
try {
|
||||
$this->db->delete(
|
||||
$this->dbTablePrefix . 'users_remembered',
|
||||
[ 'user' => $userId ]
|
||||
);
|
||||
}
|
||||
catch (Error $e) {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
protected function deleteRememberDirectiveForUserById($userId) {
|
||||
parent::deleteRememberDirectiveForUserById($userId);
|
||||
|
||||
$this->setRememberCookie(null, null, \time() - 3600);
|
||||
}
|
||||
@@ -715,36 +703,14 @@ final class Auth extends UserManager {
|
||||
if ($this->isLoggedIn()) {
|
||||
$newPassword = self::validatePassword($newPassword);
|
||||
$userId = $this->getUserId();
|
||||
$this->updatePassword($userId, $newPassword);
|
||||
$this->deleteRememberDirective($userId);
|
||||
$this->updatePasswordInternal($userId, $newPassword);
|
||||
$this->deleteRememberDirectiveForUserById($userId);
|
||||
}
|
||||
else {
|
||||
throw new NotLoggedInException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given user's password by setting it to the new specified password
|
||||
*
|
||||
* @param int $userId the ID of the user whose password should be updated
|
||||
* @param string $newPassword the new password
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
private function updatePassword($userId, $newPassword) {
|
||||
$newPassword = \password_hash($newPassword, \PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$this->db->update(
|
||||
$this->dbTablePrefix . 'users',
|
||||
[ 'password' => $newPassword ],
|
||||
[ 'id' => $userId ]
|
||||
);
|
||||
}
|
||||
catch (Error $e) {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to change the email address of the currently signed-in user (which requires confirmation)
|
||||
*
|
||||
@@ -1023,7 +989,7 @@ final class Auth extends UserManager {
|
||||
// if the password needs to be re-hashed to keep up with improving password cracking techniques
|
||||
if (\password_needs_rehash($userData['password'], \PASSWORD_DEFAULT)) {
|
||||
// create a new hash from the password and update it in the database
|
||||
$this->updatePassword($userData['id'], $password);
|
||||
$this->updatePasswordInternal($userData['id'], $password);
|
||||
}
|
||||
|
||||
if ((int) $userData['verified'] === 1) {
|
||||
@@ -1218,10 +1184,10 @@ final class Auth extends UserManager {
|
||||
$newPassword = self::validatePassword($newPassword);
|
||||
|
||||
// update the password in the database
|
||||
$this->updatePassword($resetData['user'], $newPassword);
|
||||
$this->updatePasswordInternal($resetData['user'], $newPassword);
|
||||
|
||||
// delete any remaining remember directives
|
||||
$this->deleteRememberDirective($resetData['user']);
|
||||
$this->deleteRememberDirectiveForUserById($resetData['user']);
|
||||
|
||||
try {
|
||||
$this->db->delete(
|
||||
|
@@ -182,6 +182,33 @@ abstract class UserManager {
|
||||
return $newUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given user's password by setting it to the new specified password
|
||||
*
|
||||
* @param int $userId the ID of the user whose password should be updated
|
||||
* @param string $newPassword the new password
|
||||
* @throws UnknownIdException if no user with the specified ID has been found
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
protected function updatePasswordInternal($userId, $newPassword) {
|
||||
$newPassword = \password_hash($newPassword, \PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$affected = $this->db->update(
|
||||
$this->dbTablePrefix . 'users',
|
||||
[ 'password' => $newPassword ],
|
||||
[ 'id' => $userId ]
|
||||
);
|
||||
|
||||
if ($affected === 0) {
|
||||
throw new UnknownIdException();
|
||||
}
|
||||
}
|
||||
catch (Error $e) {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a user has successfully logged in
|
||||
*
|
||||
@@ -336,4 +363,22 @@ abstract class UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an existing directive that keeps the user logged in ("remember me")
|
||||
*
|
||||
* @param int $userId the ID of the user who shouldn't be kept signed in anymore
|
||||
* @throws AuthError if an internal problem occurred (do *not* catch)
|
||||
*/
|
||||
protected function deleteRememberDirectiveForUserById($userId) {
|
||||
try {
|
||||
$this->db->delete(
|
||||
$this->dbTablePrefix . 'users_remembered',
|
||||
[ 'user' => $userId ]
|
||||
);
|
||||
}
|
||||
catch (Error $e) {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -614,6 +614,43 @@ function processRequestData(\Delight\Auth\Auth $auth) {
|
||||
return 'Username required';
|
||||
}
|
||||
}
|
||||
else if ($_POST['action'] === 'admin.changePasswordForUser') {
|
||||
if (isset($_POST['newPassword'])) {
|
||||
if (isset($_POST['id'])) {
|
||||
try {
|
||||
$auth->admin()->changePasswordForUserById($_POST['id'], $_POST['newPassword']);
|
||||
}
|
||||
catch (\Delight\Auth\UnknownIdException $e) {
|
||||
return 'unknown ID';
|
||||
}
|
||||
catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
return 'invalid password';
|
||||
}
|
||||
}
|
||||
elseif (isset($_POST['username'])) {
|
||||
try {
|
||||
$auth->admin()->changePasswordForUserByUsername($_POST['username'], $_POST['newPassword']);
|
||||
}
|
||||
catch (\Delight\Auth\UnknownUsernameException $e) {
|
||||
return 'unknown username';
|
||||
}
|
||||
catch (\Delight\Auth\AmbiguousUsernameException $e) {
|
||||
return 'ambiguous username';
|
||||
}
|
||||
catch (\Delight\Auth\InvalidPasswordException $e) {
|
||||
return 'invalid password';
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 'either ID or username required';
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 'new password required';
|
||||
}
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
else {
|
||||
throw new Exception('Unexpected action: ' . $_POST['action']);
|
||||
}
|
||||
@@ -944,6 +981,20 @@ function showGuestUserForm() {
|
||||
echo '<input type="text" name="username" placeholder="Username" /> ';
|
||||
echo '<button type="submit">Log in as user by username</button>';
|
||||
echo '</form>';
|
||||
|
||||
echo '<form action="" method="post" accept-charset="utf-8">';
|
||||
echo '<input type="hidden" name="action" value="admin.changePasswordForUser" />';
|
||||
echo '<input type="text" name="id" placeholder="ID" /> ';
|
||||
echo '<input type="text" name="newPassword" placeholder="New password" /> ';
|
||||
echo '<button type="submit">Change password for user by ID</button>';
|
||||
echo '</form>';
|
||||
|
||||
echo '<form action="" method="post" accept-charset="utf-8">';
|
||||
echo '<input type="hidden" name="action" value="admin.changePasswordForUser" />';
|
||||
echo '<input type="text" name="username" placeholder="Username" /> ';
|
||||
echo '<input type="text" name="newPassword" placeholder="New password" /> ';
|
||||
echo '<button type="submit">Change password for user by username</button>';
|
||||
echo '</form>';
|
||||
}
|
||||
|
||||
function showConfirmEmailForm() {
|
||||
|
Reference in New Issue
Block a user