Skip to content

Commit 5bbb2fd

Browse files
committed
add /password endpoint
1 parent fc79fc6 commit 5bbb2fd

File tree

5 files changed

+172
-43
lines changed

5 files changed

+172
-43
lines changed

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -708,12 +708,13 @@ Below you find more information on each of the authentication types.
708708

709709
The database authentication middleware defines three new routes:
710710

711-
method path - parameters - description
711+
method path - parameters - description
712712
---------------------------------------------------------------------------------------------------
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
717718

718719
A user can be logged in by sending it's username and password to the login endpoint (in JSON format).
719720
The authenticated user (with all it's properties) will be stored in the `$_SESSION['user']` variable.

api.php

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7558,48 +7558,80 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
75587558
}
75597559
$path = RequestUtils::getPathSegment($request, 1);
75607560
$method = $request->getMethod();
7561-
if ($method == 'POST' && in_array($path, ['login', 'register'])) {
7561+
if ($method == 'POST' && in_array($path, ['login', 'register', 'password'])) {
75627562
$body = $request->getParsedBody();
75637563
$username = isset($body->username) ? $body->username : '';
75647564
$password = isset($body->password) ? $body->password : '';
7565+
$newPassword = isset($body->newPassword) ? $body->newPassword : '';
75657566
$tableName = $this->getProperty('usersTable', 'users');
75667567
$table = $this->reflection->getTable($tableName);
75677568
$usernameColumnName = $this->getProperty('usernameColumn', 'username');
75687569
$usernameColumn = $table->getColumn($usernameColumnName);
75697570
$passwordColumnName = $this->getProperty('passwordColumn', 'password');
7570-
$passwordColumn = $table->getColumn($passwordColumnName);
7571+
$pkName = $table->getPk()->getName();
75717572
$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);
75727583
if ($path == 'register') {
75737584
if (!$registerUser) {
75747585
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
75757586
}
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+
}
75767591
$data = json_decode($registerUser, true);
75777592
$data = is_array($data) ? $data : [];
75787593
$data[$usernameColumnName] = $username;
75797594
$data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
75807595
$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);
75817602
}
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);
75897616
}
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);
75967631
}
7597-
unset($user[$passwordColumnName]);
7598-
$_SESSION['user'] = $user;
7599-
return $this->responder->success($user);
76007632
}
7633+
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
76017634
}
7602-
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
76037635
}
76047636
if ($method == 'POST' && $path == 'logout') {
76057637
if (isset($_SESSION['user'])) {
@@ -9929,6 +9961,7 @@ class ErrorCode
99299961
const BAD_OR_MISSING_XSRF_TOKEN = 1017;
99309962
const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
99319963
const PAGINATION_FORBIDDEN = 1019;
9964+
const USER_ALREADY_EXIST = 1020;
99329965

99339966
private $values = [
99349967
9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
@@ -9952,6 +9985,7 @@ class ErrorCode
99529985
1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
99539986
1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
99549987
1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
9988+
1020 => ["User '%s' already exists", ResponseFactory::CONFLICT],
99559989
];
99569990

99579991
public function __construct(int $code)

src/Tqdev/PhpCrudApi/Middleware/DbAuthMiddleware.php

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,48 +42,80 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
4242
}
4343
$path = RequestUtils::getPathSegment($request, 1);
4444
$method = $request->getMethod();
45-
if ($method == 'POST' && in_array($path, ['login', 'register'])) {
45+
if ($method == 'POST' && in_array($path, ['login', 'register', 'password'])) {
4646
$body = $request->getParsedBody();
4747
$username = isset($body->username) ? $body->username : '';
4848
$password = isset($body->password) ? $body->password : '';
49+
$newPassword = isset($body->newPassword) ? $body->newPassword : '';
4950
$tableName = $this->getProperty('usersTable', 'users');
5051
$table = $this->reflection->getTable($tableName);
5152
$usernameColumnName = $this->getProperty('usernameColumn', 'username');
5253
$usernameColumn = $table->getColumn($usernameColumnName);
5354
$passwordColumnName = $this->getProperty('passwordColumn', 'password');
54-
$passwordColumn = $table->getColumn($passwordColumnName);
55+
$pkName = $table->getPk()->getName();
5556
$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);
5667
if ($path == 'register') {
5768
if (!$registerUser) {
5869
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
5970
}
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+
}
6075
$data = json_decode($registerUser, true);
6176
$data = is_array($data) ? $data : [];
6277
$data[$usernameColumnName] = $username;
6378
$data[$passwordColumnName] = password_hash($password, PASSWORD_DEFAULT);
6479
$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);
6586
}
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);
73100
}
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);
80115
}
81-
unset($user[$passwordColumnName]);
82-
$_SESSION['user'] = $user;
83-
return $this->responder->success($user);
84116
}
117+
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
85118
}
86-
return $this->responder->error(ErrorCode::AUTHENTICATION_FAILED, $username);
87119
}
88120
if ($method == 'POST' && $path == 'logout') {
89121
if (isset($_SESSION['user'])) {

src/Tqdev/PhpCrudApi/Record/ErrorCode.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class ErrorCode
3131
const BAD_OR_MISSING_XSRF_TOKEN = 1017;
3232
const ONLY_AJAX_REQUESTS_ALLOWED = 1018;
3333
const PAGINATION_FORBIDDEN = 1019;
34+
const USER_ALREADY_EXIST = 1020;
3435

3536
private $values = [
3637
9999 => ["%s", ResponseFactory::INTERNAL_SERVER_ERROR],
@@ -54,6 +55,7 @@ class ErrorCode
5455
1017 => ["Bad or missing XSRF token", ResponseFactory::FORBIDDEN],
5556
1018 => ["Only AJAX requests allowed for '%s'", ResponseFactory::FORBIDDEN],
5657
1019 => ["Pagination forbidden", ResponseFactory::FORBIDDEN],
58+
1020 => ["User '%s' already exists", ResponseFactory::CONFLICT],
5759
];
5860

5961
public function __construct(int $code)

tests/functional/002_auth/003_db_auth.log

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,72 @@ Content-Length: 49
8080
POST /register
8181
Content-Type: application/json; charset=utf-8
8282

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+
8394
{"username":"user3","password":"pass3"}
8495
===
8596
200
8697
Content-Type: application/json; charset=utf-8
8798
Content-Length: 27
8899

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+
89149
{"id":3,"username":"user3"}
90150
===
91151
GET /me

0 commit comments

Comments
 (0)