Skip to content

Commit 05ac08c

Browse files
committed
feat: add session management to UserManagement
Add session management methods: - listSessions: List sessions for a user with pagination - revokeSession: Revoke a specific session - authenticateWithSessionCookie: Validate sealed session cookies - loadSealedSession: Create CookieSession from sealed data - getSessionFromCookie: Extract session from HTTP cookies Support injectable SessionEncryptionInterface for custom encryption providers, defaulting to HaliteSessionEncryption.
1 parent ffb5318 commit 05ac08c

2 files changed

Lines changed: 460 additions & 0 deletions

File tree

lib/UserManagement.php

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,43 @@ class UserManagement
1616
public const AUTHORIZATION_PROVIDER_GOOGLE_OAUTH = "GoogleOAuth";
1717
public const AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH = "MicrosoftOAuth";
1818

19+
/**
20+
* @var Session\SessionEncryptionInterface|null
21+
*/
22+
private $sessionEncryptor = null;
23+
24+
/**
25+
* @param Session\SessionEncryptionInterface|null $encryptor Optional encryption provider
26+
*/
27+
public function __construct(?Session\SessionEncryptionInterface $encryptor = null)
28+
{
29+
$this->sessionEncryptor = $encryptor;
30+
}
31+
32+
/**
33+
* Set the session encryptor.
34+
*
35+
* @param Session\SessionEncryptionInterface $encryptor
36+
* @return void
37+
*/
38+
public function setSessionEncryptor(Session\SessionEncryptionInterface $encryptor): void
39+
{
40+
$this->sessionEncryptor = $encryptor;
41+
}
42+
43+
/**
44+
* Get the session encryptor, defaulting to Halite.
45+
*
46+
* @return Session\SessionEncryptionInterface
47+
*/
48+
private function getSessionEncryptor(): Session\SessionEncryptionInterface
49+
{
50+
if ($this->sessionEncryptor === null) {
51+
$this->sessionEncryptor = new Session\HaliteSessionEncryption();
52+
}
53+
return $this->sessionEncryptor;
54+
}
55+
1956
/**
2057
* Create User.
2158
*
@@ -1328,4 +1365,161 @@ public function getLogoutUrl(string $sessionId, ?string $return_to = null)
13281365

13291366
return Client::generateUrl("user_management/sessions/logout", $params);
13301367
}
1368+
1369+
/**
1370+
* List sessions for a user.
1371+
*
1372+
* @param string $userId User ID
1373+
* @param array $options Additional options
1374+
* - 'limit' (int): Maximum number of records to return (default: 10)
1375+
* - 'before' (string|null): Session ID to look before
1376+
* - 'after' (string|null): Session ID to look after
1377+
* - 'order' (string|null): The order in which to paginate records
1378+
*
1379+
* @return array{?string, ?string, Resource\Session[]}
1380+
* An array containing before/after cursors and array of Session instances
1381+
* @throws Exception\WorkOSException
1382+
*/
1383+
public function listSessions(string $userId, array $options = [])
1384+
{
1385+
$path = "user_management/users/{$userId}/sessions";
1386+
1387+
$params = [
1388+
"limit" => $options['limit'] ?? self::DEFAULT_PAGE_SIZE,
1389+
"before" => $options['before'] ?? null,
1390+
"after" => $options['after'] ?? null,
1391+
"order" => $options['order'] ?? null
1392+
];
1393+
1394+
$response = Client::request(
1395+
Client::METHOD_GET,
1396+
$path,
1397+
null,
1398+
$params,
1399+
true
1400+
);
1401+
1402+
$sessions = [];
1403+
list($before, $after) = Util\Request::parsePaginationArgs($response);
1404+
1405+
foreach ($response["data"] as $responseData) {
1406+
\array_push($sessions, Resource\Session::constructFromResponse($responseData));
1407+
}
1408+
1409+
return [$before, $after, $sessions];
1410+
}
1411+
1412+
/**
1413+
* Revoke a session.
1414+
*
1415+
* @param string $sessionId Session ID
1416+
*
1417+
* @return Resource\Session The revoked session
1418+
* @throws Exception\WorkOSException
1419+
*/
1420+
public function revokeSession(string $sessionId)
1421+
{
1422+
$path = "user_management/sessions/{$sessionId}/revoke";
1423+
1424+
$response = Client::request(
1425+
Client::METHOD_POST,
1426+
$path,
1427+
null,
1428+
null,
1429+
true
1430+
);
1431+
1432+
return Resource\Session::constructFromResponse($response);
1433+
}
1434+
1435+
/**
1436+
* Authenticate with a sealed session cookie.
1437+
*
1438+
* @param string $sealedSession Encrypted session cookie data
1439+
* @param string $cookiePassword Password to decrypt the session
1440+
*
1441+
* @return Resource\SessionAuthenticationSuccessResponse|Resource\SessionAuthenticationFailureResponse
1442+
* @throws Exception\WorkOSException
1443+
*/
1444+
public function authenticateWithSessionCookie(
1445+
string $sealedSession,
1446+
string $cookiePassword
1447+
) {
1448+
if (empty($sealedSession)) {
1449+
return new Resource\SessionAuthenticationFailureResponse(
1450+
Resource\SessionAuthenticationFailureResponse::REASON_NO_SESSION_COOKIE_PROVIDED
1451+
);
1452+
}
1453+
1454+
// Tight try/catch for unsealing only
1455+
try {
1456+
$sessionData = $this->getSessionEncryptor()->unseal($sealedSession, $cookiePassword);
1457+
} catch (\Exception $e) {
1458+
return new Resource\SessionAuthenticationFailureResponse(
1459+
Resource\SessionAuthenticationFailureResponse::REASON_ENCRYPTION_ERROR
1460+
);
1461+
}
1462+
1463+
if (!isset($sessionData['access_token']) || !isset($sessionData['refresh_token'])) {
1464+
return new Resource\SessionAuthenticationFailureResponse(
1465+
Resource\SessionAuthenticationFailureResponse::REASON_INVALID_SESSION_COOKIE
1466+
);
1467+
}
1468+
1469+
// Separate try/catch for HTTP request
1470+
try {
1471+
$path = "user_management/sessions/authenticate";
1472+
$params = [
1473+
"access_token" => $sessionData['access_token'],
1474+
"refresh_token" => $sessionData['refresh_token']
1475+
];
1476+
1477+
$response = Client::request(
1478+
Client::METHOD_POST,
1479+
$path,
1480+
null,
1481+
$params,
1482+
true
1483+
);
1484+
1485+
return Resource\SessionAuthenticationSuccessResponse::constructFromResponse($response);
1486+
} catch (\Exception $e) {
1487+
return new Resource\SessionAuthenticationFailureResponse(
1488+
Resource\SessionAuthenticationFailureResponse::REASON_HTTP_ERROR
1489+
);
1490+
}
1491+
}
1492+
1493+
/**
1494+
* Load a sealed session and return a CookieSession instance.
1495+
*
1496+
* @param string $sealedSession Encrypted session cookie data
1497+
* @param string $cookiePassword Password to decrypt the session
1498+
*
1499+
* @return CookieSession
1500+
*/
1501+
public function loadSealedSession(string $sealedSession, string $cookiePassword)
1502+
{
1503+
return new CookieSession($this, $sealedSession, $cookiePassword);
1504+
}
1505+
1506+
/**
1507+
* Extract and decrypt a session from HTTP cookies.
1508+
*
1509+
* @param string $cookiePassword Password to decrypt the session
1510+
* @param string $cookieName Name of the session cookie (default: 'wos-session')
1511+
*
1512+
* @return CookieSession|null
1513+
*/
1514+
public function getSessionFromCookie(
1515+
string $cookiePassword,
1516+
string $cookieName = 'wos-session'
1517+
) {
1518+
if (!isset($_COOKIE[$cookieName])) {
1519+
return null;
1520+
}
1521+
1522+
$sealedSession = $_COOKIE[$cookieName];
1523+
return $this->loadSealedSession($sealedSession, $cookiePassword);
1524+
}
13311525
}

0 commit comments

Comments
 (0)