mirror of
https://github.com/delight-im/PHP-Auth.git
synced 2025-08-08 09:06:29 +02:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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;
|
12
README.md
12
README.md
@@ -18,9 +18,9 @@ Completely framework-agnostic and database-agnostic.
|
||||
|
||||
* PHP 5.6.0+
|
||||
* 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`)
|
||||
* 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
|
||||
|
||||
@@ -38,7 +38,9 @@ Completely framework-agnostic and database-agnostic.
|
||||
|
||||
1. Set up a database and create the required tables:
|
||||
|
||||
* [MariaDB](Database/MySQL.sql)
|
||||
* [MySQL](Database/MySQL.sql)
|
||||
* [PostgreSQL](Database/PostgreSQL.sql)
|
||||
* [SQLite](Database/SQLite.sql)
|
||||
|
||||
## Upgrading
|
||||
@@ -96,12 +98,16 @@ Migrating from an earlier version of this project? See our [upgrade guide](Migra
|
||||
```php
|
||||
// $db = new \PDO('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||
// 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');
|
||||
|
||||
// or
|
||||
|
||||
// $db = new \Delight\Db\PdoDsn('mysql:dbname=my-database;host=localhost;charset=utf8mb4', 'my-username', 'my-password');
|
||||
// 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');
|
||||
|
||||
$auth = new \Delight\Auth\Auth($db);
|
||||
@@ -115,6 +121,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 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)
|
||||
|
||||
```php
|
||||
|
@@ -510,7 +510,7 @@ final class Administration extends UserManager {
|
||||
throw new DatabaseError();
|
||||
}
|
||||
|
||||
$numberOfMatchingUsers = \count($users);
|
||||
$numberOfMatchingUsers = ($users !== null) ? \count($users) : 0;
|
||||
|
||||
if ($numberOfMatchingUsers === 1) {
|
||||
$user = $users[0];
|
||||
|
70
src/Auth.php
70
src/Auth.php
@@ -28,6 +28,8 @@ final class Auth extends UserManager {
|
||||
private $ipAddress;
|
||||
/** @var bool whether throttling should be enabled (e.g. in production) or disabled (e.g. during development) */
|
||||
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 */
|
||||
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|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 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);
|
||||
|
||||
$this->ipAddress = !empty($ipAddress) ? $ipAddress : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null);
|
||||
$this->throttling = isset($throttling) ? (bool) $throttling : true;
|
||||
$this->sessionResyncInterval = isset($sessionResyncInterval) ? ((int) $sessionResyncInterval) : (60 * 5);
|
||||
$this->rememberCookieName = self::createRememberCookieName();
|
||||
|
||||
$this->initSession();
|
||||
$this->initSessionIfNecessary();
|
||||
$this->enhanceHttpSecurity();
|
||||
|
||||
$this->processRememberDirective();
|
||||
$this->resyncSessionIfNecessary();
|
||||
}
|
||||
|
||||
/** 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 */
|
||||
@@ -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
|
||||
*
|
||||
@@ -343,6 +390,7 @@ final class Auth extends UserManager {
|
||||
unset($_SESSION[self::SESSION_FIELD_STATUS]);
|
||||
unset($_SESSION[self::SESSION_FIELD_ROLES]);
|
||||
unset($_SESSION[self::SESSION_FIELD_REMEMBERED]);
|
||||
unset($_SESSION[self::SESSION_FIELD_LAST_RESYNC]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -38,6 +38,8 @@ abstract class UserManager {
|
||||
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) */
|
||||
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 */
|
||||
protected $db;
|
||||
@@ -205,6 +207,7 @@ abstract class UserManager {
|
||||
$_SESSION[self::SESSION_FIELD_STATUS] = (int) $status;
|
||||
$_SESSION[self::SESSION_FIELD_ROLES] = (int) $roles;
|
||||
$_SESSION[self::SESSION_FIELD_REMEMBERED] = $remembered;
|
||||
$_SESSION[self::SESSION_FIELD_LAST_RESYNC] = \time();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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');
|
||||
// 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');
|
||||
|
||||
$auth = new \Delight\Auth\Auth($db);
|
||||
|
Reference in New Issue
Block a user