mirror of
https://github.com/delight-im/PHP-Auth.git
synced 2025-08-08 00:56:28 +02:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
095b8ccc70 | ||
|
550a6d0355 | ||
|
c494e0fa13 | ||
|
d7d9899167 | ||
|
05165a44a6 | ||
|
c3f2097750 | ||
|
395a065fd4 | ||
|
627c592891 | ||
|
2a6d1c4f7d | ||
|
a63e5ec053 | ||
|
4115340927 | ||
|
09dac6a5f5 | ||
|
3a7a860c6d | ||
|
131aea3ded | ||
|
e14f3d1925 | ||
|
1d54ff2f6b | ||
|
ec6afdad48 | ||
|
58e69fdd0e | ||
|
e7e174b05d | ||
|
8f35cc9965 | ||
|
142ccc362f | ||
|
bce31f9cfc | ||
|
3ddc7af1b4 | ||
|
62d9e44aa4 | ||
|
1121685cef | ||
|
2f9bab4779 | ||
|
89e99d727d | ||
|
21341d3c18 |
57
Database/PostgreSQL.sql
Normal file
57
Database/PostgreSQL.sql
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
-- PHP-Auth (https://github.com/delight-im/PHP-Auth)
|
||||||
|
-- Copyright (c) delight.im (https://www.delight.im/)
|
||||||
|
-- Licensed under the MIT License (https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "users" (
|
||||||
|
"id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
|
"email" VARCHAR(249) UNIQUE NOT NULL,
|
||||||
|
"password" VARCHAR(255) NOT NULL,
|
||||||
|
"username" VARCHAR(100) DEFAULT NULL,
|
||||||
|
"status" SMALLINT NOT NULL DEFAULT '0' CHECK ("status" >= 0),
|
||||||
|
"verified" SMALLINT NOT NULL DEFAULT '0' CHECK ("verified" >= 0),
|
||||||
|
"resettable" SMALLINT NOT NULL DEFAULT '1' CHECK ("resettable" >= 0),
|
||||||
|
"roles_mask" INTEGER NOT NULL DEFAULT '0' CHECK ("roles_mask" >= 0),
|
||||||
|
"registered" INTEGER NOT NULL CHECK ("registered" >= 0),
|
||||||
|
"last_login" INTEGER DEFAULT NULL CHECK ("last_login" >= 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "users_confirmations" (
|
||||||
|
"id" SERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
|
"user_id" INTEGER NOT NULL CHECK ("user_id" >= 0),
|
||||||
|
"email" VARCHAR(249) NOT NULL,
|
||||||
|
"selector" VARCHAR(16) UNIQUE NOT NULL,
|
||||||
|
"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" (
|
||||||
|
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
|
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
||||||
|
"selector" VARCHAR(24) UNIQUE NOT NULL,
|
||||||
|
"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" (
|
||||||
|
"id" BIGSERIAL PRIMARY KEY CHECK ("id" >= 0),
|
||||||
|
"user" INTEGER NOT NULL CHECK ("user" >= 0),
|
||||||
|
"selector" VARCHAR(20) UNIQUE NOT NULL,
|
||||||
|
"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" (
|
||||||
|
"bucket" VARCHAR(44) PRIMARY KEY,
|
||||||
|
"tokens" REAL NOT NULL CHECK ("tokens" >= 0),
|
||||||
|
"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;
|
42
README.md
42
README.md
@@ -18,9 +18,9 @@ Completely framework-agnostic and database-agnostic.
|
|||||||
|
|
||||||
* PHP 5.6.0+
|
* PHP 5.6.0+
|
||||||
* PDO (PHP Data Objects) extension (`pdo`)
|
* PDO (PHP Data Objects) extension (`pdo`)
|
||||||
* MySQL Native Driver (`mysqlnd`) **or** SQLite driver (`sqlite`)
|
* MySQL Native Driver (`mysqlnd`) **or** PostgreSQL driver (`pgsql`) **or** SQLite driver (`sqlite`)
|
||||||
* OpenSSL extension (`openssl`)
|
* OpenSSL extension (`openssl`)
|
||||||
* MySQL 5.5.3+ **or** MariaDB 5.5.23+ **or** SQLite 3.14.1+ **or** other SQL databases that you create the [schema](Database) for
|
* MySQL 5.5.3+ **or** MariaDB 5.5.23+ **or** PostgreSQL 9.5.10+ **or** SQLite 3.14.1+ **or** [other SQL databases](Database)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -38,7 +38,9 @@ Completely framework-agnostic and database-agnostic.
|
|||||||
|
|
||||||
1. Set up a database and create the required tables:
|
1. Set up a database and create the required tables:
|
||||||
|
|
||||||
|
* [MariaDB](Database/MySQL.sql)
|
||||||
* [MySQL](Database/MySQL.sql)
|
* [MySQL](Database/MySQL.sql)
|
||||||
|
* [PostgreSQL](Database/PostgreSQL.sql)
|
||||||
* [SQLite](Database/SQLite.sql)
|
* [SQLite](Database/SQLite.sql)
|
||||||
|
|
||||||
## Upgrading
|
## Upgrading
|
||||||
@@ -80,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)
|
* [Taking roles away from users](#taking-roles-away-from-users)
|
||||||
* [Checking roles](#checking-roles-1)
|
* [Checking roles](#checking-roles-1)
|
||||||
* [Impersonating users (logging in as user)](#impersonating-users-logging-in-as-user)
|
* [Impersonating users (logging in as user)](#impersonating-users-logging-in-as-user)
|
||||||
|
* [Changing a user’s password](#changing-a-users-password)
|
||||||
* [Cookies](#cookies)
|
* [Cookies](#cookies)
|
||||||
* [Renaming the library’s cookies](#renaming-the-librarys-cookies)
|
* [Renaming the library’s cookies](#renaming-the-librarys-cookies)
|
||||||
* [Defining the domain scope for cookies](#defining-the-domain-scope-for-cookies)
|
* [Defining the domain scope for cookies](#defining-the-domain-scope-for-cookies)
|
||||||
@@ -96,12 +99,16 @@ Migrating from an earlier version of this project? See our [upgrade guide](Migra
|
|||||||
```php
|
```php
|
||||||
// $db = new \PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
// $db = new \PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||||
// or
|
// or
|
||||||
|
// $db = new \PDO('pgsql:dbname=my-database;host=localhost;port=5432', 'my-username', 'my-password');
|
||||||
|
// or
|
||||||
// $db = new \PDO('sqlite:../Databases/my-database.sqlite');
|
// $db = new \PDO('sqlite:../Databases/my-database.sqlite');
|
||||||
|
|
||||||
// or
|
// or
|
||||||
|
|
||||||
// $db = new \Delight\Db\PdoDsn('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
// $db = new \Delight\Db\PdoDsn('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||||
// or
|
// or
|
||||||
|
// $db = new \Delight\Db\PdoDsn('pgsql:dbname=my-database;host=localhost;port=5432', 'my-username', 'my-password');
|
||||||
|
// or
|
||||||
// $db = new \Delight\Db\PdoDsn('sqlite:../Databases/my-database.sqlite');
|
// $db = new \Delight\Db\PdoDsn('sqlite:../Databases/my-database.sqlite');
|
||||||
|
|
||||||
$auth = new \Delight\Auth\Auth($db);
|
$auth = new \Delight\Auth\Auth($db);
|
||||||
@@ -115,6 +122,8 @@ Should your database tables for this library need a common prefix, e.g. `my_user
|
|||||||
|
|
||||||
During development, you may want to disable the request limiting or throttling performed by this library. To do so, pass `false` to the constructor as the fourth argument, which is named `$throttling`. The feature is enabled by default.
|
During development, you may want to disable the request limiting or throttling performed by this library. To do so, pass `false` to the constructor as the fourth argument, which is named `$throttling`. The feature is enabled by default.
|
||||||
|
|
||||||
|
During the lifetime of a session, some user data may be changed remotely, either by a client in another session or by an administrator. That means this information must be regularly resynchronized with its authoritative source in the database, which this library does automatically. By default, this happens every five minutes. If you want to change this interval, pass a custom interval in seconds to the constructor as the fifth argument, which is named `$sessionResyncInterval`.
|
||||||
|
|
||||||
### Registration (sign up)
|
### Registration (sign up)
|
||||||
|
|
||||||
```php
|
```php
|
||||||
@@ -993,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
|
### Cookies
|
||||||
|
|
||||||
This library uses two cookies to keep state on the client: The first, whose name you can retrieve using
|
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;
|
namespace Delight\Auth;
|
||||||
|
|
||||||
use Delight\Db\PdoDatabase;
|
use Delight\Db\PdoDatabase;
|
||||||
|
use Delight\Db\PdoDsn;
|
||||||
use Delight\Db\Throwable\Error;
|
use Delight\Db\Throwable\Error;
|
||||||
|
|
||||||
require_once __DIR__ . '/Exceptions.php';
|
require_once __DIR__ . '/Exceptions.php';
|
||||||
@@ -17,12 +18,10 @@ require_once __DIR__ . '/Exceptions.php';
|
|||||||
final class Administration extends UserManager {
|
final class Administration extends UserManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @param PdoDatabase|PdoDsn|\PDO $databaseConnection the database connection to operate on
|
||||||
*
|
|
||||||
* @param PdoDatabase $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
|
* @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);
|
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
|
* 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();
|
throw new DatabaseError();
|
||||||
}
|
}
|
||||||
|
|
||||||
$numberOfMatchingUsers = \count($users);
|
$numberOfMatchingUsers = ($users !== null) ? \count($users) : 0;
|
||||||
|
|
||||||
if ($numberOfMatchingUsers === 1) {
|
if ($numberOfMatchingUsers === 1) {
|
||||||
$user = $users[0];
|
$user = $users[0];
|
||||||
|
122
src/Auth.php
122
src/Auth.php
@@ -28,6 +28,8 @@ final class Auth extends UserManager {
|
|||||||
private $ipAddress;
|
private $ipAddress;
|
||||||
/** @var bool whether throttling should be enabled (e.g. in production) or disabled (e.g. during development) */
|
/** @var bool whether throttling should be enabled (e.g. in production) or disabled (e.g. during development) */
|
||||||
private $throttling;
|
private $throttling;
|
||||||
|
/** @var int the interval in seconds after which to resynchronize the session data with its authoritative source in the database */
|
||||||
|
private $sessionResyncInterval;
|
||||||
/** @var string the name of the cookie used for the 'remember me' feature */
|
/** @var string the name of the cookie used for the 'remember me' feature */
|
||||||
private $rememberCookieName;
|
private $rememberCookieName;
|
||||||
|
|
||||||
@@ -36,31 +38,36 @@ final class Auth extends UserManager {
|
|||||||
* @param string $ipAddress the IP address that should be used instead of the default setting (if any), e.g. when behind a proxy
|
* @param string $ipAddress the IP address that should be used instead of the default setting (if any), e.g. when behind a proxy
|
||||||
* @param string|null $dbTablePrefix (optional) the prefix for the names of all database tables used by this component
|
* @param string|null $dbTablePrefix (optional) the prefix for the names of all database tables used by this component
|
||||||
* @param bool|null $throttling (optional) whether throttling should be enabled (e.g. in production) or disabled (e.g. during development)
|
* @param bool|null $throttling (optional) whether throttling should be enabled (e.g. in production) or disabled (e.g. during development)
|
||||||
|
* @param int|null $sessionResyncInterval (optional) the interval in seconds after which to resynchronize the session data with its authoritative source in the database
|
||||||
*/
|
*/
|
||||||
public function __construct($databaseConnection, $ipAddress = null, $dbTablePrefix = null, $throttling = null) {
|
public function __construct($databaseConnection, $ipAddress = null, $dbTablePrefix = null, $throttling = null, $sessionResyncInterval = null) {
|
||||||
parent::__construct($databaseConnection, $dbTablePrefix);
|
parent::__construct($databaseConnection, $dbTablePrefix);
|
||||||
|
|
||||||
$this->ipAddress = !empty($ipAddress) ? $ipAddress : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null);
|
$this->ipAddress = !empty($ipAddress) ? $ipAddress : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null);
|
||||||
$this->throttling = isset($throttling) ? (bool) $throttling : true;
|
$this->throttling = isset($throttling) ? (bool) $throttling : true;
|
||||||
|
$this->sessionResyncInterval = isset($sessionResyncInterval) ? ((int) $sessionResyncInterval) : (60 * 5);
|
||||||
$this->rememberCookieName = self::createRememberCookieName();
|
$this->rememberCookieName = self::createRememberCookieName();
|
||||||
|
|
||||||
$this->initSession();
|
$this->initSessionIfNecessary();
|
||||||
$this->enhanceHttpSecurity();
|
$this->enhanceHttpSecurity();
|
||||||
|
|
||||||
$this->processRememberDirective();
|
$this->processRememberDirective();
|
||||||
|
$this->resyncSessionIfNecessary();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initializes the session and sets the correct configuration */
|
/** Initializes the session and sets the correct configuration */
|
||||||
private function initSession() {
|
private function initSessionIfNecessary() {
|
||||||
// use cookies to store session IDs
|
if (\session_status() === \PHP_SESSION_NONE) {
|
||||||
\ini_set('session.use_cookies', 1);
|
// use cookies to store session IDs
|
||||||
// use cookies only (do not send session IDs in URLs)
|
\ini_set('session.use_cookies', 1);
|
||||||
\ini_set('session.use_only_cookies', 1);
|
// use cookies only (do not send session IDs in URLs)
|
||||||
// do not send session IDs in URLs
|
\ini_set('session.use_only_cookies', 1);
|
||||||
\ini_set('session.use_trans_sid', 0);
|
// 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)
|
// start the session (requests a cookie to be written on the client)
|
||||||
@Session::start();
|
@Session::start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Improves the application's security over HTTP(S) by setting specific headers */
|
/** Improves the application's security over HTTP(S) by setting specific headers */
|
||||||
@@ -136,6 +143,46 @@ final class Auth extends UserManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resyncSessionIfNecessary() {
|
||||||
|
// if the user is signed in
|
||||||
|
if ($this->isLoggedIn()) {
|
||||||
|
if (!isset($_SESSION[self::SESSION_FIELD_LAST_RESYNC])) {
|
||||||
|
$_SESSION[self::SESSION_FIELD_LAST_RESYNC] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's time for resynchronization
|
||||||
|
if (($_SESSION[self::SESSION_FIELD_LAST_RESYNC] + $this->sessionResyncInterval) <= \time()) {
|
||||||
|
// fetch the authoritative data from the database again
|
||||||
|
try {
|
||||||
|
$authoritativeData = $this->db->selectRow(
|
||||||
|
'SELECT email, username, status, roles_mask FROM ' . $this->dbTablePrefix . 'users WHERE id = ?',
|
||||||
|
[ $this->getUserId() ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Error $e) {
|
||||||
|
throw new DatabaseError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user's data has been found
|
||||||
|
if (!empty($authoritativeData)) {
|
||||||
|
// update the session data
|
||||||
|
$_SESSION[self::SESSION_FIELD_EMAIL] = $authoritativeData['email'];
|
||||||
|
$_SESSION[self::SESSION_FIELD_USERNAME] = $authoritativeData['username'];
|
||||||
|
$_SESSION[self::SESSION_FIELD_STATUS] = (int) $authoritativeData['status'];
|
||||||
|
$_SESSION[self::SESSION_FIELD_ROLES] = (int) $authoritativeData['roles_mask'];
|
||||||
|
}
|
||||||
|
// if no data has been found for the user
|
||||||
|
else {
|
||||||
|
// their account may have been deleted so they should be signed out
|
||||||
|
$this->logOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember that we've just performed resynchronization
|
||||||
|
$_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to sign up a user
|
* Attempts to sign up a user
|
||||||
*
|
*
|
||||||
@@ -332,7 +379,7 @@ final class Auth extends UserManager {
|
|||||||
// if a user ID was set
|
// if a user ID was set
|
||||||
if (isset($userId)) {
|
if (isset($userId)) {
|
||||||
// delete any existing remember directives
|
// delete any existing remember directives
|
||||||
$this->deleteRememberDirective($userId);
|
$this->deleteRememberDirectiveForUserById($userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all session variables maintained by this library
|
// remove all session variables maintained by this library
|
||||||
@@ -343,6 +390,7 @@ final class Auth extends UserManager {
|
|||||||
unset($_SESSION[self::SESSION_FIELD_STATUS]);
|
unset($_SESSION[self::SESSION_FIELD_STATUS]);
|
||||||
unset($_SESSION[self::SESSION_FIELD_ROLES]);
|
unset($_SESSION[self::SESSION_FIELD_ROLES]);
|
||||||
unset($_SESSION[self::SESSION_FIELD_REMEMBERED]);
|
unset($_SESSION[self::SESSION_FIELD_REMEMBERED]);
|
||||||
|
unset($_SESSION[self::SESSION_FIELD_LAST_RESYNC]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,22 +439,8 @@ final class Auth extends UserManager {
|
|||||||
$this->setRememberCookie($selector, $token, $expires);
|
$this->setRememberCookie($selector, $token, $expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function deleteRememberDirectiveForUserById($userId) {
|
||||||
* Clears an existing directive that keeps the user logged in ("remember me")
|
parent::deleteRememberDirectiveForUserById($userId);
|
||||||
*
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setRememberCookie(null, null, \time() - 3600);
|
$this->setRememberCookie(null, null, \time() - 3600);
|
||||||
}
|
}
|
||||||
@@ -669,36 +703,14 @@ final class Auth extends UserManager {
|
|||||||
if ($this->isLoggedIn()) {
|
if ($this->isLoggedIn()) {
|
||||||
$newPassword = self::validatePassword($newPassword);
|
$newPassword = self::validatePassword($newPassword);
|
||||||
$userId = $this->getUserId();
|
$userId = $this->getUserId();
|
||||||
$this->updatePassword($userId, $newPassword);
|
$this->updatePasswordInternal($userId, $newPassword);
|
||||||
$this->deleteRememberDirective($userId);
|
$this->deleteRememberDirectiveForUserById($userId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new NotLoggedInException();
|
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)
|
* Attempts to change the email address of the currently signed-in user (which requires confirmation)
|
||||||
*
|
*
|
||||||
@@ -977,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 the password needs to be re-hashed to keep up with improving password cracking techniques
|
||||||
if (\password_needs_rehash($userData['password'], \PASSWORD_DEFAULT)) {
|
if (\password_needs_rehash($userData['password'], \PASSWORD_DEFAULT)) {
|
||||||
// create a new hash from the password and update it in the database
|
// 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) {
|
if ((int) $userData['verified'] === 1) {
|
||||||
@@ -1172,10 +1184,10 @@ final class Auth extends UserManager {
|
|||||||
$newPassword = self::validatePassword($newPassword);
|
$newPassword = self::validatePassword($newPassword);
|
||||||
|
|
||||||
// update the password in the database
|
// update the password in the database
|
||||||
$this->updatePassword($resetData['user'], $newPassword);
|
$this->updatePasswordInternal($resetData['user'], $newPassword);
|
||||||
|
|
||||||
// delete any remaining remember directives
|
// delete any remaining remember directives
|
||||||
$this->deleteRememberDirective($resetData['user']);
|
$this->deleteRememberDirectiveForUserById($resetData['user']);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->db->delete(
|
$this->db->delete(
|
||||||
|
@@ -38,6 +38,8 @@ abstract class UserManager {
|
|||||||
const SESSION_FIELD_ROLES = 'auth_roles';
|
const SESSION_FIELD_ROLES = 'auth_roles';
|
||||||
/** @var string session field for whether the user who is currently signed in (if any) has been remembered (instead of them having authenticated actively) */
|
/** @var string session field for whether the user who is currently signed in (if any) has been remembered (instead of them having authenticated actively) */
|
||||||
const SESSION_FIELD_REMEMBERED = 'auth_remembered';
|
const SESSION_FIELD_REMEMBERED = 'auth_remembered';
|
||||||
|
/** @var string session field for the UNIX timestamp in seconds of the session data's last resynchronization with its authoritative source in the database */
|
||||||
|
const SESSION_FIELD_LAST_RESYNC = 'auth_last_resync';
|
||||||
|
|
||||||
/** @var PdoDatabase the database connection to operate on */
|
/** @var PdoDatabase the database connection to operate on */
|
||||||
protected $db;
|
protected $db;
|
||||||
@@ -180,6 +182,33 @@ abstract class UserManager {
|
|||||||
return $newUserId;
|
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
|
* Called when a user has successfully logged in
|
||||||
*
|
*
|
||||||
@@ -205,6 +234,7 @@ abstract class UserManager {
|
|||||||
$_SESSION[self::SESSION_FIELD_STATUS] = (int) $status;
|
$_SESSION[self::SESSION_FIELD_STATUS] = (int) $status;
|
||||||
$_SESSION[self::SESSION_FIELD_ROLES] = (int) $roles;
|
$_SESSION[self::SESSION_FIELD_ROLES] = (int) $roles;
|
||||||
$_SESSION[self::SESSION_FIELD_REMEMBERED] = $remembered;
|
$_SESSION[self::SESSION_FIELD_REMEMBERED] = $remembered;
|
||||||
|
$_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -333,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,8 @@ require __DIR__.'/../vendor/autoload.php';
|
|||||||
|
|
||||||
$db = new \PDO('mysql:dbname=php_auth;host=127.0.0.1;charset=utf8mb4', 'root', 'monkey');
|
$db = new \PDO('mysql:dbname=php_auth;host=127.0.0.1;charset=utf8mb4', 'root', 'monkey');
|
||||||
// or
|
// or
|
||||||
|
// $db = new \PDO('pgsql:dbname=php_auth;host=127.0.0.1;port=5432', 'postgres', 'monkey');
|
||||||
|
// or
|
||||||
// $db = new \PDO('sqlite:../Databases/php_auth.sqlite');
|
// $db = new \PDO('sqlite:../Databases/php_auth.sqlite');
|
||||||
|
|
||||||
$auth = new \Delight\Auth\Auth($db);
|
$auth = new \Delight\Auth\Auth($db);
|
||||||
@@ -612,6 +614,43 @@ function processRequestData(\Delight\Auth\Auth $auth) {
|
|||||||
return 'Username required';
|
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 {
|
else {
|
||||||
throw new Exception('Unexpected action: ' . $_POST['action']);
|
throw new Exception('Unexpected action: ' . $_POST['action']);
|
||||||
}
|
}
|
||||||
@@ -942,6 +981,20 @@ function showGuestUserForm() {
|
|||||||
echo '<input type="text" name="username" placeholder="Username" /> ';
|
echo '<input type="text" name="username" placeholder="Username" /> ';
|
||||||
echo '<button type="submit">Log in as user by username</button>';
|
echo '<button type="submit">Log in as user by username</button>';
|
||||||
echo '</form>';
|
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() {
|
function showConfirmEmailForm() {
|
||||||
|
Reference in New Issue
Block a user