File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -40,6 +40,11 @@ function initDatabase() {
4040 password TEXT NOT NULL,
4141 data TEXT
4242 ) ' ,
43+ 'CREATE TABLE IF NOT EXISTS ipAttempts (
44+ ip VARCHAR(255) NOT NULL PRIMARY KEY,
45+ type VARCHAR(255) NOT NULL,
46+ expires NOT NULL
47+ ) ' ,
4348 ];
4449
4550 try {
Original file line number Diff line number Diff line change 1+ <?php
2+ namespace Pdsinterop \PhpSolid ;
3+
4+ class IpAttempts {
5+ private static $ pdo ;
6+ private static function connect () {
7+ if (!isset (self ::$ pdo )) {
8+ self ::$ pdo = new \PDO ("sqlite: " . DBPATH );
9+ }
10+ }
11+
12+ public static function logFailedAttempt ($ ip , $ type , $ expires ) {
13+ self ::connect ();
14+
15+ $ query = self ::$ pdo ->prepare (
16+ 'INSERT INTO ipAttempts VALUES(:ip, :type, :expires) '
17+ );
18+ $ query ->execute ([
19+ ':ip ' => $ ip ,
20+ ':type ' => $ type ,
21+ ':expires ' => $ expires ->getTimestamp ()
22+ ]);
23+ }
24+
25+ public static function getAttemptsCount ($ ip , $ type ) {
26+ self ::connect ();
27+
28+ $ now = new \DateTime ();
29+ $ query = self ::$ pdo ->prepare (
30+ 'SELECT count(ip) as count FROM ipAttempts WHERE ip=:ip AND type=:type AND expires > :now '
31+ );
32+ $ query ->execute ([
33+ ':ip ' => $ ip ,
34+ ':type ' => $ type ,
35+ ':now ' => $ now ->getTimestamp ()
36+ ]);
37+ $ result = $ query ->fetch ();
38+ if (isset ($ result ['count ' ])) {
39+ return $ result ['count ' ];
40+ }
41+ return 0 ;
42+ }
43+ public static function cleanupAttempts () {
44+ self ::connect ();
45+
46+ $ now = new \DateTime ();
47+ $ query = self ::$ pdo ->prepare (
48+ 'DELETE FROM ipAttempts WHERE expires < :now '
49+ );
50+ $ query ->execute ([
51+ ':now ' => $ now ->getTimestamp ()
52+ ]);
53+ }
54+ }
Original file line number Diff line number Diff line change 99 use Pdsinterop \PhpSolid \ClientRegistration ;
1010 use Pdsinterop \PhpSolid \User ;
1111 use Pdsinterop \PhpSolid \Mailer ;
12+ use Pdsinterop \PhpSolid \IpAttempts ;
1213
1314 $ request = explode ("? " , $ _SERVER ['REQUEST_URI ' ], 2 )[0 ];
1415 $ method = $ _SERVER ['REQUEST_METHOD ' ];
284285 break ;
285286 case "/login/password " :
286287 case "/login/password/ " :
288+ $ failureCount = IpAttempts::getAttemptsCount ($ _SERVER ['REMOTE_ADDR ' ], "login " );
289+ if ($ failureCount > 5 ) {
290+ header ("HTTP/1.1 400 Bad Request " );
291+ exit ();
292+ }
287293 if (User::checkPassword ($ _POST ['username ' ], $ _POST ['password ' ])) {
288294 if (!isset ($ _POST ['redirect_uri ' ]) || $ _POST ['redirect_uri ' ] === '' ) {
289295 header ("Location: /dashboard/ " );
290296 exit ();
291297 }
292298 header ("Location: " . urldecode ($ _POST ['redirect_uri ' ])); // FIXME: Do we need to harden this?
293299 } else {
300+ IpAttempts::logFailedAttempt ($ _SERVER ['REMOTE_ADDR ' ], "login " , time () + 3600 );
294301 header ("Location: /login/ " );
295302 }
296303 break ;
405412 if (!file_exists (CLEANUP_FILE ) || (filemtime (CLEANUP_FILE ) < time ())) {
406413 touch (CLEANUP_FILE , time () + 3600 );
407414 User::cleanupTokens ();
415+ IpAttempts::cleanupAttempts ();
408416 }
409417 break ;
410418 case "OPTIONS " :
You can’t perform that action at this time.
0 commit comments