Browse Source

add /password endpoint

Maurits van der Schee 3 years ago
parent
commit
5bbb2fd32b

+ 6
- 5
README.md View File

@@ -708,12 +708,13 @@ Below you find more information on each of the authentication types.
708 708
 
709 709
 The database authentication middleware defines three new routes:
710 710
 
711
-    method path       - parameters               - description
711
+    method path       - parameters                      - description
712 712
     ---------------------------------------------------------------------------------------------------
713
-    POST   /register  - username + password      - adds a user with given username and password
714
-    POST   /login     - username + password      - logs a user in by username and password
715
-    POST   /logout    -                          - logs out the currently logged in user
716
-    GET    /me        -                          - returns the user as which you're currently logged in
713
+    GET    /me        -                                 - returns the user that is currently logged in
714
+    POST   /register  - username, password              - adds a user with given username and password
715
+    POST   /login     - username, password              - logs a user in by username and password
716
+    POST   /password  - username, password, newPassword - updates the password of the logged in user
717
+    POST   /logout    -                                 - logs out the currently logged in user
717 718
 
718 719
 A user can be logged in by sending it's username and password to the login endpoint (in JSON format).
719 720
 The authenticated user (with all it's properties) will be stored in the `$_SESSION['user']` variable.

+ 53
- 19
api.php View File

@@ -7558,48 +7558,80 @@ namespace Tqdev\PhpCrudApi\Middleware {
7558 7558
             }
7559 7559
             $path = RequestUtils::getPathSegment($request, 1);
7560 7560
             $method = $request->getMethod();
7561
-            if ($method == 'POST' && in_array($path, ['login', 'register'])) {
7561
+            if ($method == 'POST' && in_array($path, ['login', 'register', 'password'])) {
7562 7562
                 $body = $request->getParsedBody();
7563 7563
                 $username = isset($body->username) ? $body->username : '';
7564 7564
                 $password = isset($body->password) ? $body->password : '';
7565
+                $newPassword = isset($body->newPassword) ? $body->newPassword : '';
7565 7566
                 $tableName = $this->getProperty('usersTable', 'users');
7566 7567
                 $table = $this->reflection->getTable($tableName);
7567 7568
                 $usernameColumnName = $this->getProperty('usernameColumn', 'username');
7568 7569
                 $usernameColumn = $table->getColumn($usernameColumnName);
7569 7570
                 $passwordColumnName = $this->getProperty('passwordColumn', 'password');
7570
-                $passwordColumn = $table->getColumn($passwordColumnName);
7571
+                $pkName = $table->getPk()->getName();
7571 7572
                 $registerUser = $this->getProperty('registerUser', '');
7573
+                $condition = new ColumnCondition($usernameColumn, 'eq', $username);
7574
+                $returnedColumns = $this->getProperty('returnedColumns', '');
7575
+                if (!$returnedColumns) {
7576
+                    $columnNames = $table->getColumnNames();
7577
+                } else {
7578
+                    $columnNames = array_map('trim', explode(',', $returnedColumns));
7579
+                    $columnNames[] = $passwordColumnName;
7580
+                    $columnNames[] = $pkName;
7581
+                }
7582
+                $columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
7572 7583
                 if ($path == 'register') {
7573 7584
                     if (!$registerUser) {
7574 7585
                         return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7575 7586
                     }
7587
+                    $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
7588
+                    if (!empty($users)) {
7589
+                        return $this->responder->error(ErrorCode::USER_ALREADY_EXIST, $username);
7590
+                    }
7576 7591
                     $data = json_decode($registerUser, true);
7577 7592
                     $data = is_array($data) ? $data : [];
7578 7593
                     $data[$usernameColumnName] = $username;
7579 7594
                     $data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
7580 7595
                     $this->db->createSingle($table, $data);
7596
+                    $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
7597
+                    foreach ($users as $user) {
7598
+                        unset($user[$passwordColumnName]);
7599
+                        return $this->responder->success($user);
7600
+                    }
7601
+                    return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7581 7602
                 }
7582
-                $condition = new ColumnCondition($usernameColumn, 'eq', $username);
7583
-                $returnedColumns = $this->getProperty('returnedColumns', '');
7584
-                if (!$returnedColumns) {
7585
-                    $columnNames = $table->getColumnNames();
7586
-                } else {
7587
-                    $columnNames = array_map('trim', explode(',', $returnedColumns));
7588
-                    $columnNames[] = $passwordColumnName;
7603
+                if ($path == 'login') {
7604
+                    $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
7605
+                    foreach ($users as $user) {
7606
+                        if (password_verify($password, $user[$passwordColumnName]) == 1) {
7607
+                            if (!headers_sent()) {
7608
+                                session_regenerate_id(true);
7609
+                            }
7610
+                            unset($user[$passwordColumnName]);
7611
+                            $_SESSION['user'] = $user;
7612
+                            return $this->responder->success($user);
7613
+                        }
7614
+                    }
7615
+                    return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7589 7616
                 }
7590
-                $columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
7591
-                $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
7592
-                foreach ($users as $user) {
7593
-                    if (password_verify($password, $user[$passwordColumnName]) == 1) {
7594
-                        if (!headers_sent()) {
7595
-                            session_regenerate_id(true);
7617
+                if ($path == 'password') {
7618
+                    if ($username != ($_SESSION['user'][$usernameColumnName] ?? '')) {
7619
+                        return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7620
+                    }
7621
+                    $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
7622
+                    foreach ($users as $user) {
7623
+                        if (password_verify($password, $user[$passwordColumnName]) == 1) {
7624
+                            if (!headers_sent()) {
7625
+                                session_regenerate_id(true);
7626
+                            }
7627
+                            $data = [$passwordColumnName => password_hash($newPassword, PASSWORD_DEFAULT)];
7628
+                            $this->db->updateSingle($table, $data, $user[$pkName]);
7629
+                            unset($user[$passwordColumnName]);
7630
+                            return $this->responder->success($user);
7596 7631
                         }
7597
-                        unset($user[$passwordColumnName]);
7598
-                        $_SESSION['user'] = $user;
7599
-                        return $this->responder->success($user);
7600 7632
                     }
7633
+                    return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7601 7634
                 }
7602
-                return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
7603 7635
             }
7604 7636
             if ($method == 'POST' && $path == 'logout') {
7605 7637
                 if (isset($_SESSION['user'])) {
@@ -9929,6 +9961,7 @@ namespace Tqdev\PhpCrudApi\Record {
9929 9961
         const BAD_OR_MISSING_XSRF_TOKEN = 1017;
9930 9962
         const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
9931 9963
         const PAGINATION_FORBIDDEN = 1019;
9964
+        const USER_ALREADY_EXIST = 1020;
9932 9965
 
9933 9966
         private $values = [
9934 9967
             9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
@@ -9952,6 +9985,7 @@ namespace Tqdev\PhpCrudApi\Record {
9952 9985
             1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
9953 9986
             1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
9954 9987
             1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
9988
+            1020 => ["User '%s' already exists", ResponseFactory::CONFLICT],
9955 9989
         ];
9956 9990
 
9957 9991
         public function __construct(int $code)

+ 51
- 19
src/Tqdev/PhpCrudApi/Middleware/DbAuthMiddleware.php View File

@@ -42,48 +42,80 @@ class DbAuthMiddleware extends Middleware
42 42
         }
43 43
         $path = RequestUtils::getPathSegment($request, 1);
44 44
         $method = $request->getMethod();
45
-        if ($method == 'POST' && in_array($path, ['login', 'register'])) {
45
+        if ($method == 'POST' && in_array($path, ['login', 'register', 'password'])) {
46 46
             $body = $request->getParsedBody();
47 47
             $username = isset($body->username) ? $body->username : '';
48 48
             $password = isset($body->password) ? $body->password : '';
49
+            $newPassword = isset($body->newPassword) ? $body->newPassword : '';
49 50
             $tableName = $this->getProperty('usersTable', 'users');
50 51
             $table = $this->reflection->getTable($tableName);
51 52
             $usernameColumnName = $this->getProperty('usernameColumn', 'username');
52 53
             $usernameColumn = $table->getColumn($usernameColumnName);
53 54
             $passwordColumnName = $this->getProperty('passwordColumn', 'password');
54
-            $passwordColumn = $table->getColumn($passwordColumnName);
55
+            $pkName = $table->getPk()->getName();
55 56
             $registerUser = $this->getProperty('registerUser', '');
57
+            $condition = new ColumnCondition($usernameColumn, 'eq', $username);
58
+            $returnedColumns = $this->getProperty('returnedColumns', '');
59
+            if (!$returnedColumns) {
60
+                $columnNames = $table->getColumnNames();
61
+            } else {
62
+                $columnNames = array_map('trim', explode(',', $returnedColumns));
63
+                $columnNames[] = $passwordColumnName;
64
+                $columnNames[] = $pkName;
65
+            }
66
+            $columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
56 67
             if ($path == 'register') {
57 68
                 if (!$registerUser) {
58 69
                     return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
59 70
                 }
71
+                $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
72
+                if (!empty($users)) {
73
+                    return $this->responder->error(ErrorCode::USER_ALREADY_EXIST, $username);
74
+                }
60 75
                 $data = json_decode($registerUser, true);
61 76
                 $data = is_array($data) ? $data : [];
62 77
                 $data[$usernameColumnName] = $username;
63 78
                 $data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
64 79
                 $this->db->createSingle($table, $data);
80
+                $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
81
+                foreach ($users as $user) {
82
+                    unset($user[$passwordColumnName]);
83
+                    return $this->responder->success($user);
84
+                }
85
+                return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
65 86
             }
66
-            $condition = new ColumnCondition($usernameColumn, 'eq', $username);
67
-            $returnedColumns = $this->getProperty('returnedColumns', '');
68
-            if (!$returnedColumns) {
69
-                $columnNames = $table->getColumnNames();
70
-            } else {
71
-                $columnNames = array_map('trim', explode(',', $returnedColumns));
72
-                $columnNames[] = $passwordColumnName;
87
+            if ($path == 'login') {
88
+                $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
89
+                foreach ($users as $user) {
90
+                    if (password_verify($password, $user[$passwordColumnName]) == 1) {
91
+                        if (!headers_sent()) {
92
+                            session_regenerate_id(true);
93
+                        }
94
+                        unset($user[$passwordColumnName]);
95
+                        $_SESSION['user'] = $user;
96
+                        return $this->responder->success($user);
97
+                    }
98
+                }
99
+                return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
73 100
             }
74
-            $columnOrdering = $this->ordering->getDefaultColumnOrdering($table);
75
-            $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
76
-            foreach ($users as $user) {
77
-                if (password_verify($password, $user[$passwordColumnName]) == 1) {
78
-                    if (!headers_sent()) {
79
-                        session_regenerate_id(true);
101
+            if ($path == 'password') {
102
+                if ($username != ($_SESSION['user'][$usernameColumnName] ?? '')) {
103
+                    return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
104
+                }
105
+                $users = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, 0, 1);
106
+                foreach ($users as $user) {
107
+                    if (password_verify($password, $user[$passwordColumnName]) == 1) {
108
+                        if (!headers_sent()) {
109
+                            session_regenerate_id(true);
110
+                        }
111
+                        $data = [$passwordColumnName => password_hash($newPassword, PASSWORD_DEFAULT)];
112
+                        $this->db->updateSingle($table, $data, $user[$pkName]);
113
+                        unset($user[$passwordColumnName]);
114
+                        return $this->responder->success($user);
80 115
                     }
81
-                    unset($user[$passwordColumnName]);
82
-                    $_SESSION['user'] = $user;
83
-                    return $this->responder->success($user);
84 116
                 }
117
+                return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
85 118
             }
86
-            return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
87 119
         }
88 120
         if ($method == 'POST' && $path == 'logout') {
89 121
             if (isset($_SESSION['user'])) {

+ 2
- 0
src/Tqdev/PhpCrudApi/Record/ErrorCode.php View File

@@ -31,6 +31,7 @@ class ErrorCode
31 31
     const BAD_OR_MISSING_XSRF_TOKEN = 1017;
32 32
     const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
33 33
     const PAGINATION_FORBIDDEN = 1019;
34
+    const USER_ALREADY_EXIST = 1020;
34 35
 
35 36
     private $values = [
36 37
         9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
@@ -54,6 +55,7 @@ class ErrorCode
54 55
         1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
55 56
         1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
56 57
         1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
58
+        1020 => ["User '%s' already exists", ResponseFactory::CONFLICT],
57 59
     ];
58 60
 
59 61
     public function __construct(int $code)

+ 60
- 0
tests/functional/002_auth/003_db_auth.log View File

@@ -80,12 +80,72 @@ Content-Length: 49
80 80
 POST /register
81 81
 Content-Type: application/json; charset=utf-8
82 82
 
83
+{"username":"user2","password":"pass2"}
84
+===
85
+409
86
+Content-Type: application/json; charset=utf-8
87
+Content-Length: 53
88
+
89
+{"code":1020,"message":"User 'user2' already exists"}
90
+===
91
+POST /register
92
+Content-Type: application/json; charset=utf-8
93
+
83 94
 {"username":"user3","password":"pass3"}
84 95
 ===
85 96
 200
86 97
 Content-Type: application/json; charset=utf-8
87 98
 Content-Length: 27
88 99
 
100
+{"id":3,"username":"user3"}
101
+===
102
+POST /login
103
+Content-Type: application/json; charset=utf-8
104
+
105
+{"username":"user3","password":"pass3"}
106
+===
107
+200
108
+Content-Type: application/json; charset=utf-8
109
+Content-Length: 27
110
+
111
+{"id":3,"username":"user3"}
112
+===
113
+GET /me
114
+===
115
+200
116
+Content-Type: application/json; charset=utf-8
117
+Content-Length: 27
118
+
119
+{"id":3,"username":"user3"}
120
+===
121
+POST /password
122
+Content-Type: application/json; charset=utf-8
123
+
124
+{"username":"user3","password":"pass3","newPassword":"secret3"}
125
+===
126
+200
127
+Content-Type: application/json; charset=utf-8
128
+Content-Length: 27
129
+
130
+{"id":3,"username":"user3"}
131
+===
132
+POST /logout
133
+===
134
+200
135
+Content-Type: application/json; charset=utf-8
136
+Content-Length: 27
137
+
138
+{"id":3,"username":"user3"}
139
+===
140
+POST /login
141
+Content-Type: application/json; charset=utf-8
142
+
143
+{"username":"user3","password":"secret3"}
144
+===
145
+200
146
+Content-Type: application/json; charset=utf-8
147
+Content-Length: 27
148
+
89 149
 {"id":3,"username":"user3"}
90 150
 ===
91 151
 GET /me

Loading…
Cancel
Save