Skip to content
This repository has been archived by the owner on Jan 6, 2023. It is now read-only.

V8.0.0 #1414

Merged
merged 17 commits into from
Nov 12, 2019
Merged

V8.0.0 #1414

Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions src/core/Directus/Services/AuthService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Directus\Services;

use function Directus\get_directus_path;
use function Directus\get_api_project_from_request;
use function Directus\get_url;
use Directus\Authentication\Exception\ExpiredRequestTokenException;
use Directus\Authentication\Exception\InvalidRequestTokenException;
use Directus\Authentication\Exception\InvalidTokenException;
Expand Down Expand Up @@ -68,22 +71,22 @@ public function loginWithCredentials($email, $password, $otp=null, $mode = null)
$tfa_enforced = $usersService->has2FAEnforced($user->getId());

switch($mode){
case DirectusUserSessionsTableGateway::TOKEN_COOKIE :
case DirectusUserSessionsTableGateway::TOKEN_COOKIE :
$user = $this->findOrCreateStaticToken($user);
$responseData['user'] = $user;
break;
case DirectusUserSessionsTableGateway::TOKEN_JWT :
default :
case DirectusUserSessionsTableGateway::TOKEN_JWT :
default :
$token = $this->generateAuthToken($user);
$user = $user->toArray();
$responseData = [
'token' => $token,
'user' => $user
];

}
$responseObject['data'] = $responseData;

if(!is_null($user)){
$needs2FA = $tfa_enforced && $user['2fa_secret'] == null;
if($needs2FA){
Expand All @@ -100,7 +103,7 @@ public function loginWithCredentials($email, $password, $otp=null, $mode = null)
* @param array $user
*
* @return array
*
*
*/
public function findOrCreateStaticToken(&$user)
{
Expand Down Expand Up @@ -227,12 +230,12 @@ public function handleAuthenticationRequestCallback($name, $generateRequestToken
$user = $this->authenticateWithEmail($serviceUser->getEmail());

switch($mode){
case DirectusUserSessionsTableGateway::TOKEN_COOKIE :
case DirectusUserSessionsTableGateway::TOKEN_COOKIE :
$user = $this->findOrCreateStaticToken($user);
$responseData['user'] = $user;
break;
case DirectusUserSessionsTableGateway::TOKEN_JWT :
default :
case DirectusUserSessionsTableGateway::TOKEN_JWT :
default :
$token = $generateRequestToken ? $this->generateRequestToken($user) : $this->generateAuthToken($user);
$responseData = [
'token' => $token,
Expand All @@ -258,7 +261,7 @@ public function authenticateWithToken($token, $ignoreOrigin = false)
} else {
$authenticated = $this->getAuth()->authenticateWithPrivateToken($token);
}

return $authenticated;
}

Expand Down Expand Up @@ -384,6 +387,7 @@ public function generateRequestToken(UserInterface $user)
* @param $email
*/
public function sendResetPasswordToken($email)

{
$this->validate(['email' => $email], ['email' => 'required|email']);

Expand All @@ -393,10 +397,14 @@ public function sendResetPasswordToken($email)

$resetToken = $auth->generateResetPasswordToken($user);

\Directus\send_forgot_password_email($user->toArray(), $resetToken);
// Sending the project key in the query param makes sure the app will use the correct project
// to send the new password to
$resetUrl = get_url() . 'admin/#/reset-password?token=' . $resetToken . '&project=' . get_api_project_from_request();

\Directus\send_forgot_password_email($user->toArray(), $resetUrl);
}

public function resetPasswordWithToken($token)
public function resetPasswordWithToken($token, $newPassword)
{
if (!JWTUtils::isJWT($token)) {
throw new InvalidResetPasswordTokenException($token);
Expand Down Expand Up @@ -427,12 +435,9 @@ public function resetPasswordWithToken($token)
throw new InvalidResetPasswordTokenException($token);
}

$newPassword = StringUtils::randomString(16);
$userProvider->update($user, [
'password' => $auth->hashPassword($newPassword)
]);

\Directus\send_reset_password_email($user->toArray(), $newPassword);
}

public function refreshToken($token)
Expand Down
37 changes: 25 additions & 12 deletions src/endpoints/Auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Directus\Application\Route;
use function Directus\array_get;
use function Directus\get_directus_setting;
use function Directus\get_directus_path;
use function Directus\get_project_session_cookie_name;
use function Directus\get_request_authorization_token;
use function Directus\encrypt_static_token;
Expand Down Expand Up @@ -36,7 +37,7 @@ public function __invoke(Application $app)
$app->post('/logout/{user}', [$this, 'logoutFromAll']);
$app->post('/logout/{user}/{id}', [$this, 'logoutFromOne']);
$app->post('/password/request', [$this, 'forgotPassword']);
$app->get('/password/reset/{token}', [$this, 'resetPassword']);
$app->post('/password/reset', [$this, 'resetPassword']);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passwords are now being passed in the body instead of being auto-generated.

The previous method was done that way because the API didn't know where the app was, so by utilizing GET requests, we could circumvent that. The UX now is way nicer

$app->post('/refresh', [$this, 'refresh']);
$app->get('/sso', [$this, 'listSsoAuthServices']);
$app->post('/sso/access_token', [$this, 'ssoAccessToken']);
Expand Down Expand Up @@ -255,7 +256,8 @@ public function resetPassword(Request $request, Response $response)
$authService = $this->container->get('services')->get('auth');

$authService->resetPasswordWithToken(
$request->getAttribute('token')
$request->getParsedBodyParam('token'),
$request->getParsedBodyParam('password')
);

return $this->responseWithData($request, $response, []);
Expand Down Expand Up @@ -291,10 +293,10 @@ public function listSsoAuthServices(Request $request, Response $response)
{
/** @var AuthService $authService */
$authService = $this->container->get('services')->get('auth');

/** @var Social $externalAuth */
$externalAuth = $this->container->get('external_auth');

$services = [];
foreach ($externalAuth->getAll() as $name => $provider) {
$services[] = $authService->getSsoBasicInfo($name);
Expand Down Expand Up @@ -367,18 +369,22 @@ public function ssoServiceCallback(Request $request, Response $response)
{
/** @var AuthService $authService */
$authService = $this->container->get('services')->get('auth');

$session = $this->container->get('session');
// TODO: Implement a pull method
$redirectUrl = $session->get('sso_origin_url');
$session->remove('sso_origin_url');
$mode = $session->get('mode');

$redirectUrl = $mode == DirectusUserSessionsTableGateway::TOKEN_COOKIE
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bjgajjar I'm thinking we might want to add source=app or something flag, so we can redirect to /admin only when it makes sense 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/admin will be set in redirect_url - the requested origin will be set as sso_origin_url - this way we don't need any flag to redirect back the system.

But yes - IMO, we should return the array of responses when this redirect_url is not set in the system.

? get_directus_path() . '/admin/#/'
: $session->get('sso_origin_url');

$needs2FA = false;
$responseData = [];
$urlParams = [];

try {
$responseData = $authService->handleAuthenticationRequestCallback(
$request->getAttribute('service'),
!!$redirectUrl,
true,
$mode
);

Expand All @@ -388,6 +394,7 @@ public function ssoServiceCallback(Request $request, Response $response)
if($tfa_enforced || !is_null($responseData['data']['user']['2fa_secret'])){
throw new SsoNotAllowedException();
}

switch($mode){
case DirectusUserSessionsTableGateway::TOKEN_COOKIE :
$response = $this->storeCookieSession($request,$response,$responseData['data']);
Expand All @@ -410,7 +417,7 @@ public function ssoServiceCallback(Request $request, Response $response)
$urlParams['error'] = true;
}


if ($redirectUrl) {
$redirectQueryString = parse_url($redirectUrl, PHP_URL_QUERY);
$redirectUrlParts = explode('?', $redirectUrl);
Expand All @@ -419,10 +426,16 @@ public function ssoServiceCallback(Request $request, Response $response)
if (is_array($redirectQueryParams)) {
$urlParams = array_merge($redirectQueryParams, $urlParams);
}
$urlToRedirect = !empty($urlParams) ? $redirectUrl . '?' . http_build_query($urlParams) : $redirectUrl;
$response = $response->withRedirect($urlToRedirect);

if (!empty($urlParams) && $mode == DirectusUserSessionsTableGateway::TOKEN_COOKIE) {
$redirectUrl .= 'login?' . http_build_query($urlParams);
} else {
$redirectUrl .= '?' . http_build_query($urlParams);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need to pass $urlParams if it's empty? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The else also runs for jwt mode

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there's a possibility that $urlparams is empty.

 if(!empty($urlParams)){
   $redirectUrl .= DirectusUserSessionsTableGateway::TOKEN_COOKIE ? 'login?' . http_build_query($urlParams) : '?' . http_build_query($urlParams);
}

Will this work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to hardcode to /login anymore with the redirect_url param we're adding

}

$response = $response->withRedirect($redirectUrl);
}else{
$response = $response->withRedirect('/admin');
$response = $response->withRedirect($redirectUrl);
}

$session->remove('mode');
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/Home.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Home extends Route
{
public function __invoke(Request $request, Response $response)
{
$response = $response->withRedirect('/admin');
$response = $response->withRedirect('./admin/');
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sure the redirect works well when the API is nested in another folder, eg /api/admin

return $this->responseWithData($request, $response, []);
}
}
28 changes: 3 additions & 25 deletions src/helpers/mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,42 +135,20 @@ function parse_twig($viewPath, array $data)
}
}

if (!function_exists('send_reset_password_email')) {
/**
* Sends a new password email
*
* @param $user
* @param string $password
*/
function send_reset_password_email($user, $password)
{
$data = [
'new_password' => $password,
'user_full_name' => get_user_full_name($user),
];
send_mail_with_template('reset-password.twig', $data, function (Message $message) use ($user) {
$message->setSubject(
sprintf('New Temporary Password: %s', get_directus_setting('project_name', ''))
);
$message->setTo($user['email']);
});
}
}

if (!function_exists('send_forgot_password_email')) {
/**
* Sends a new reset password email
*
* @param array $user
* @param string $token
*/
function send_forgot_password_email(array $user, $token)
function send_forgot_password_email(array $user, $url)
{
$data = [
'reset_token' => $token,
'reset_url' => $url,
'user_full_name' => get_user_full_name($user),
];
send_mail_with_template('forgot-password.twig', $data, function (Message $message) use ($user) {
send_mail_with_template('reset-password.twig', $data, function (Message $message) use ($user) {
$message->setSubject(
sprintf('Password Reset Request: %s', get_directus_setting('project_name', ''))
);
Expand Down
13 changes: 0 additions & 13 deletions src/mail/forgot-password.twig

This file was deleted.

8 changes: 3 additions & 5 deletions src/mail/reset-password.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

<p>Hey {{ user_full_name }},</p>

<p>Here is a temporary password to access Directus:</p>
<p>You requested to reset your password, click here to reset your password:</p>

<p>{{ new_password }}</p>
<p><a href="{{ reset_url }}">Reset my password</a></p>

<p>Once you log in, you can change your password via the User Settings menu.</p>

<p>Love,<br>Directus</p>
<p> Love,<br>Directus</p>

{% endblock %}