Skip to content

Commit b8833d7

Browse files
committed
Move logic away from idp/index.html
1 parent 1598bda commit b8833d7

File tree

3 files changed

+519
-343
lines changed

3 files changed

+519
-343
lines changed

lib/Api/Accounts.php

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
<?php
2+
namespace Pdsinterop\PhpSolid\Api;
3+
4+
use Pdsinterop\PhpSolid\Server;
5+
use Pdsinterop\PhpSolid\ClientRegistration;
6+
use Pdsinterop\PhpSolid\User;
7+
use Pdsinterop\PhpSolid\Session;
8+
use Pdsinterop\PhpSolid\Mailer;
9+
use Pdsinterop\PhpSolid\IpAttempts;
10+
11+
class Accounts {
12+
public static function requireLoggedInUser() {
13+
$user = User::getUser(Session::getLoggedInUser());
14+
if (!$user) {
15+
switch ($_SERVER['REQUEST_METHOD']) {
16+
case "GET":
17+
header("Location: /login/?redirect_uri=" . urlencode($_SERVER['REQUEST_URI']));
18+
exit();
19+
break;
20+
default:
21+
header("HTTP/1.0 400 Bad Request");
22+
exit();
23+
break;
24+
}
25+
}
26+
}
27+
28+
public static function respondToDashboard() {
29+
$user = User::getUser(Session::getLoggedInUser());
30+
echo "Logged in as " . $user['webId'];
31+
}
32+
33+
public static function respondToLogout() {
34+
$user = User::getUser(Session::getLoggedInUser());
35+
if ($user) {
36+
session_destroy();
37+
}
38+
header("Location: /login/");
39+
exit();
40+
}
41+
42+
public static function respondToAccountVerify() {
43+
$verifyData = [
44+
'email' => $_POST['email']
45+
];
46+
47+
$verifyToken = User::saveVerifyToken('verify', $verifyData);
48+
Mailer::sendVerify($verifyToken);
49+
50+
$responseData = "OK";
51+
header("HTTP/1.1 201 Created");
52+
header("Content-type: application/json");
53+
echo json_encode($responseData, JSON_PRETTY_PRINT);
54+
}
55+
56+
public static function respondToAccountNew() {
57+
$verifyToken = User::getVerifyToken($_POST['confirm']);
58+
if (!$verifyToken) {
59+
error_log("Could not read verify token");
60+
header("HTTP/1.1 400 Bad Request");
61+
exit();
62+
}
63+
if ($verifyToken['email'] !== $_POST['email']) {
64+
error_log("Verify token does not match email");
65+
header("HTTP/1.1 400 Bad Request");
66+
exit();
67+
}
68+
if (User::userEmailExists($_POST['email'])) {
69+
error_log("Account already exists");
70+
header("HTTP/1.1 400 Bad Request");
71+
exit();
72+
}
73+
if (!$_POST['password'] === $_POST['repeat_password']) {
74+
error_log("Password repeat does not match");
75+
header("HTTP/1.1 400 Bad Request");
76+
exit();
77+
}
78+
79+
$newUser = [
80+
"email" => $_POST['email'],
81+
"password" => $_POST['password']
82+
];
83+
84+
$createdUser = User::createUser($newUser);
85+
if (!$createdUser) {
86+
error_log("Failed to create user");
87+
header("HTTP/1.1 400 Bad Request");
88+
exit();
89+
}
90+
Mailer::sendAccountCreated($createdUser);
91+
92+
$responseData = array(
93+
"webId" => $createdUser['webId']
94+
);
95+
header("HTTP/1.1 201 Created");
96+
header("Content-type: application/json");
97+
Session::start($_POST['email']);
98+
echo json_encode($responseData, JSON_PRETTY_PRINT);
99+
}
100+
101+
public static function respondToAccountResetPassword() {
102+
if (!User::userEmailExists($_POST['email'])) {
103+
header("HTTP/1.1 200 OK"); // Return OK even when user is not found;
104+
header("Content-type: application/json");
105+
echo json_encode("OK");
106+
exit();
107+
}
108+
$verifyData = [
109+
'email' => $_POST['email']
110+
];
111+
112+
$verifyToken = User::saveVerifyToken('passwordReset', $verifyData);
113+
Mailer::sendResetPassword($verifyToken);
114+
header("HTTP/1.1 200 OK");
115+
header("Content-type: application/json");
116+
echo json_encode("OK");
117+
}
118+
119+
public static function respondToAccountChangePassword() {
120+
$verifyToken = User::getVerifyToken($_POST['token']);
121+
if (!$verifyToken) {
122+
header("HTTP/1.1 400 Bad Request");
123+
exit();
124+
}
125+
$result = User::setUserPassword($verifyToken['email'], $_POST['newPassword']);
126+
if (!$result) {
127+
header("HTTP/1.1 400 Bad Request");
128+
exit();
129+
}
130+
header("HTTP/1.1 200 OK");
131+
header("Content-type: application/json");
132+
echo json_encode("OK");
133+
}
134+
135+
public static function respondToAccountDelete() {
136+
if (!User::userEmailExists($_POST['email'])) {
137+
header("HTTP/1.1 200 OK"); // Return OK even when user is not found;
138+
header("Content-type: application/json");
139+
echo json_encode("OK");
140+
exit();
141+
}
142+
$verifyData = [
143+
'email' => $_POST['email']
144+
];
145+
146+
$verifyToken = User::saveVerifyToken('deleteAccount', $verifyData);
147+
Mailer::sendDeleteAccount($verifyToken);
148+
header("HTTP/1.1 200 OK");
149+
header("Content-type: application/json");
150+
echo json_encode("OK");
151+
}
152+
153+
public static function respondToAccountDeleteConfirm() {
154+
$verifyToken = User::getVerifyToken($_POST['token']);
155+
if (!$verifyToken) {
156+
header("HTTP/1.1 400 Bad Request");
157+
exit();
158+
}
159+
User::deleteAccount($verifyToken['email']);
160+
header("HTTP/1.1 200 OK");
161+
header("Content-type: application/json");
162+
echo json_encode("OK");
163+
}
164+
165+
public static function respondToLogin() {
166+
$failureCount = IpAttempts::getAttemptsCount($_SERVER['REMOTE_ADDR'], "login");
167+
if ($failureCount > 5) {
168+
header("HTTP/1.1 400 Bad Request");
169+
exit();
170+
}
171+
if (User::checkPassword($_POST['username'], $_POST['password'])) {
172+
Session::start($_POST['username']);
173+
if (!isset($_POST['redirect_uri']) || $_POST['redirect_uri'] === '') {
174+
header("Location: /dashboard/");
175+
exit();
176+
}
177+
header("Location: " . urldecode($_POST['redirect_uri'])); // FIXME: Do we need to harden this?
178+
} else {
179+
IpAttempts::logFailedAttempt($_SERVER['REMOTE_ADDR'], "login", time() + 3600);
180+
header("Location: /login/");
181+
}
182+
}
183+
184+
public static function respondToRegister() {
185+
$postData = file_get_contents("php://input");
186+
$clientData = json_decode($postData, true);
187+
if (!isset($clientData)) {
188+
header("HTTP/1.1 400 Bad request");
189+
return;
190+
}
191+
$parsedOrigin = parse_url($clientData['redirect_uris'][0]);
192+
$origin = $parsedOrigin['scheme'] . '://' . $parsedOrigin['host'];
193+
if (isset($parsedOrigin['port'])) {
194+
$origin .= ":" . $parsedOrigin['port'];
195+
}
196+
197+
198+
$generatedClientId = md5(random_bytes(32));
199+
$generatedClientSecret = md5(random_bytes(32));
200+
201+
$clientData['client_id_issued_at'] = time();
202+
$clientData['client_id'] = $generatedClientId;
203+
$clientData['client_secret'] = $generatedClientSecret;
204+
$clientData['origin'] = $origin;
205+
ClientRegistration::saveClientRegistration($clientData);
206+
207+
$client = ClientRegistration::getRegistration($generatedClientId);
208+
209+
$responseData = array(
210+
'redirect_uris' => $client['redirect_uris'],
211+
'client_id' => $client['client_id'],
212+
'client_secret' => $client['client_secret'],
213+
'response_types' => array('code'),
214+
'grant_types' => array('authorization_code', 'refresh_token'),
215+
'application_type' => $client['application_type'] ?? 'web',
216+
'client_name' => $client['client_name'] ?? $client['client_id'],
217+
'id_token_signed_response_alg' => 'RS256',
218+
'token_endpoint_auth_method' => 'client_secret_basic',
219+
'client_id_issued_at' => $client['client_id_issued_at'],
220+
'client_secret_expires_at' => 0
221+
);
222+
header("HTTP/1.1 201 Created");
223+
header("Content-type: application/json");
224+
echo json_encode($responseData, JSON_PRETTY_PRINT);
225+
}
226+
227+
public static function respondToSharing() {
228+
$clientId = $_POST['client_id'];
229+
$userId = $user['userId'];
230+
if ($_POST['consent'] === 'true') {
231+
User::allowClientForUser($clientId, $userId);
232+
}
233+
$returnUrl = urldecode($_POST['returnUrl']);
234+
header("Location: $returnUrl");
235+
}
236+
237+
public static function respondToToken() {
238+
$authServer = Server::getAuthServer();
239+
$tokenGenerator = Server::getTokenGenerator();
240+
241+
$requestFactory = new \Laminas\Diactoros\ServerRequestFactory();
242+
$request = $requestFactory->fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
243+
$requestBody = $request->getParsedBody();
244+
245+
$grantType = isset($requestBody['grant_type']) ? $requestBody['grant_type'] : null;
246+
$clientId = isset($requestBody['client_id']) ? $requestBody['client_id'] : null;
247+
switch ($grantType) {
248+
case "authorization_code":
249+
$code = $requestBody['code'];
250+
$codeInfo = $tokenGenerator->getCodeInfo($code);
251+
$userId = $codeInfo['user_id'];
252+
if (!$clientId) {
253+
$clientId = $codeInfo['client_id'];
254+
}
255+
break;
256+
case "refresh_token":
257+
$refreshToken = $requestBody['refresh_token'];
258+
$tokenInfo = $tokenGenerator->getCodeInfo($refreshToken); // FIXME: getCodeInfo should be named 'decrypt' or 'getInfo'?
259+
$userId = $tokenInfo['user_id'];
260+
if (!$clientId) {
261+
$clientId = $tokenInfo['client_id'];
262+
}
263+
break;
264+
default:
265+
$userId = false;
266+
break;
267+
}
268+
269+
$httpDpop = $request->getServerParams()['HTTP_DPOP'];
270+
271+
$response = $authServer->respondToAccessTokenRequest($request);
272+
273+
if (isset($userId)) {
274+
$response = $tokenGenerator->addIdTokenToResponse(
275+
$response,
276+
$clientId,
277+
$userId,
278+
($_SESSION['nonce'] ?? ''),
279+
Server::getKeys()['privateKey'],
280+
$httpDpop
281+
);
282+
}
283+
284+
Server::respond($response);
285+
}
286+
}

0 commit comments

Comments
 (0)