HEX
Server: Apache
System: Linux server1.ariadata.co 4.18.0-553.120.1.el8_10.x86_64 #1 SMP Mon Apr 20 18:04:27 EDT 2026 x86_64
User: nepi (1051)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system,show_source,popen,proc_open
Upload Files
File: /home/nepi/public_html/src/core/Directus/Services/AuthService.php
<?php

namespace Directus\Services;

use function Directus\get_directus_path;
use function Directus\get_api_project_from_request;
use function Directus\get_url;
use Directus\Util\DateTimeUtils;
use Directus\Authentication\Exception\ExpiredRequestTokenException;
use Directus\Authentication\Exception\InvalidRequestTokenException;
use Directus\Authentication\Exception\InvalidTokenException;
use Directus\Authentication\Sso\AbstractSocialProvider;
use Directus\Authentication\Exception\ExpiredResetPasswordToken;
use Directus\Authentication\Exception\InvalidResetPasswordTokenException;
use Directus\Authentication\Exception\UserNotFoundException;
use Directus\Authentication\Exception\UserWithEmailNotFoundException;
use Directus\Authentication\Exception\TFAEnforcedException;
use Directus\Authentication\Sso\OneSocialProvider;
use Directus\Authentication\Provider;
use Directus\Authentication\Sso\Social;
use Directus\Authentication\Sso\TwoSocialProvider;
use Directus\Authentication\User\UserInterface;
use Directus\Database\Schema\SchemaManager;
use Directus\Database\TableGateway\DirectusActivityTableGateway;
use Directus\Database\TableGateway\DirectusUserSessionsTableGateway;
use Directus\Exception\UnauthorizedException;
use Directus\Exception\UnprocessableEntityException;
use Directus\Util\ArrayUtils;
use Directus\Util\JWTUtils;
use Directus\Util\StringUtils;
use Zend\Db\Sql\Update;

class AuthService extends AbstractService
{
    const AUTH_VALIDATION_ERROR_CODE = 114;

    /**
     * Gets the user token using the authentication email/password combination
     *
     * @param string $email
     * @param string $password
     * @param string $otp
     *
     * @return array
     *
     * @throws UnauthorizedException
     */
    public function loginWithCredentials($email, $password, $otp = null, $mode = null)
    {
        $this->validateCredentials($email, $password, $otp);

        /** @var Provider $auth */
        $auth = $this->container->get('auth');

        /** @var UserInterface $user */
        $user = $auth->login([
            'email' => $email,
            'password' => $password,
            'otp' => $otp
        ]);

        $hookEmitter = $this->container->get('hook_emitter');
        $hookEmitter->run('auth.request:credentials', [$user]);

        // TODO: Move to the hook above
        /** @var DirectusActivityTableGateway $activityTableGateway */
        $activityTableGateway = $this->createTableGateway('directus_activity', false);
        $activityTableGateway->recordLogin($user->get('id'));

        /** @var UsersService $usersService */
        $usersService = new UsersService($this->container);
        $tfa_enforced = $usersService->has2FAEnforced($user->getId());

        switch ($mode) {
            case DirectusUserSessionsTableGateway::TOKEN_COOKIE:
                $user = $this->findOrCreateStaticToken($user);
                $responseData['user'] = $user;
                break;
            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) {
                $responseObject['error'] = [
                    'code' => TFAEnforcedException::ERROR_CODE,
                    'message' => TFAEnforcedException::ERROR_MESSAGE
                ];
            }
        }
        return $responseObject;
    }

    /**
     * @param array $user
     *
     * @return array
     *
     */
    public function findOrCreateStaticToken(&$user)
    {
        $user = $user->toArray();
        if (empty($user['token'])) {
            $token = StringUtils::randomString(24, false);
            $userTable = $this->createTableGateway(SchemaManager::COLLECTION_USERS, false);
            $Update = new Update(SchemaManager::COLLECTION_USERS);
            $Update->set(['token' => $token]);
            $Update->where([
                'id' => $user['id']
            ]);
            $userTable->updateWith($Update);
            $user['token'] = $token;
        }
        return $user;
    }

    /**
     * @param string $name
     *
     * @return array
     */
    public function getAuthenticationRequestInfo($name)
    {
        return [
            'data' => $this->getSsoAuthorizationInfo($name)
        ];
    }

    /**
     * Gets the basic information of a sso service
     *
     * @param string $name
     *
     * @return array
     */
    public function getSsoBasicInfo($name)
    {
        /** @var Social $socialAuth */
        $socialAuth = $this->container->get('external_auth');
        /** @var AbstractSocialProvider $service */
        $service = $socialAuth->get($name);
        $basePath = $this->container->get('path_base');

        $iconUrl = null;
        $type = $service->getConfig()->get('custom') === true ? 'custom' : 'core';
        $iconPath = sprintf('/extensions/%s/auth/%s/icon.svg', $type, $name);
        if (file_exists($basePath . '/public' . $iconPath)) {
            $iconUrl = $iconPath;
        }

        return [
            'name' => $name,
            'icon' => $iconUrl
        ];
    }

    /**
     * @param string $name
     *
     * @return array
     */
    public function getSsoAuthorizationInfo($name)
    {
        /** @var Social $socialAuth */
        $socialAuth = $this->container->get('external_auth');
        /** @var AbstractSocialProvider $service */
        $service = $socialAuth->get($name);

        $authorizationInfo = [
            'authorization_url' => $service->getRequestAuthorizationUrl()
        ];

        if ($service instanceof TwoSocialProvider) {
            $authorizationInfo['state'] = $service->getProvider()->getState();
        }

        return $authorizationInfo;
    }

    /**
     * @param string $name
     *
     * @return array
     */
    public function getSsoCallbackInfo($name)
    {
        /** @var Social $socialAuth */
        $socialAuth = $this->container->get('external_auth');
        /** @var AbstractSocialProvider $service */
        $service = $socialAuth->get($name);

        return [
            'callback_url' => $service->getRequestAuthorizationUrl()
        ];
    }

    /**
     * Gets the given SSO service information
     *
     * @param string $name
     *
     * @return array
     */
    public function getSsoInfo($name)
    {
        return array_merge(
            $this->getSsoBasicInfo($name),
            $this->getSsoAuthorizationInfo($name),
            $this->getSsoCallbackInfo($name)
        );
    }

    public function handleAuthenticationRequestCallback($name, $generateRequestToken = false, $mode = null)
    {
        /** @var Social $socialAuth */
        $socialAuth = $this->container->get('external_auth');
        /** @var AbstractSocialProvider $service */
        $service = $socialAuth->get($name);

        $serviceUser = $service->handle();

        $user = $this->authenticateWithEmail($serviceUser->getEmail());

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

        return [
            'data' => $responseData
        ];
    }

    /**
     * @param string $token
     * @param bool $ignoreOrigin
     *
     * @return UserInterface
     */
    public function authenticateWithToken($token, $ignoreOrigin = false)
    {
        if (JWTUtils::isJWT($token)) {
            $authenticated = $this->getAuth()->authenticateWithToken($token, $ignoreOrigin);
        } else {
            $authenticated = $this->getAuth()->authenticateWithPrivateToken($token);
        }

        return $authenticated;
    }

    /**
     * Authenticates a user with the given email
     *
     * @param $email
     *
     * @return \Directus\Authentication\User\User
     *
     * @throws UserWithEmailNotFoundException
     */
    public function authenticateWithEmail($email)
    {
        return $this->getAuth()->authenticateWithEmail($email);
    }

    /**
     * Authenticate an user with the SSO authorization code
     *
     * @param string $service
     * @param array $params
     *
     * @return array
     */
    public function authenticateWithSsoCode($service, array $params)
    {
        /** @var Social $socialAuth */
        $socialAuth = $this->container->get('external_auth');
        /** @var AbstractSocialProvider $service */
        $service = $socialAuth->get($service);

        if ($service instanceof OneSocialProvider) {
            $data = ArrayUtils::pick($params, ['oauth_token', 'oauth_verifier']);
        } else {
            $data = ArrayUtils::pick($params, ['code']);
        }

        $serviceUser = $service->getUserFromCode($data);
        $user = $this->authenticateWithEmail($serviceUser->getEmail());

        return [
            'data' => [
                'token' => $this->generateAuthToken($user)
            ]
        ];
    }

    /**
     * Gets the access token from a sso request token
     *
     * @param string $token
     *
     * @return array
     *
     * @throws ExpiredRequestTokenException
     * @throws InvalidRequestTokenException
     * @throws InvalidTokenException
     */
    public function authenticateWithSsoRequestToken($token)
    {
        if (!JWTUtils::isJWT($token)) {
            throw new InvalidRequestTokenException();
        }

        if (JWTUtils::hasExpired($token)) {
            throw new ExpiredRequestTokenException();
        }

        $payload = JWTUtils::getPayload($token);

        if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_SSO_REQUEST_TOKEN, $payload)) {
            throw new InvalidRequestTokenException();
        }

        $auth = $this->getAuth();
        $auth->validatePayloadOrigin($payload);

        $user = $auth->findUserWithConditions([
            'id' => $payload->id
        ]);

        return [
            'data' => [
                'token' => $this->generateAuthToken($user)
            ]
        ];
    }

    /**
     * Generates JWT Token
     *
     * @param UserInterface $user
     *
     * @return string
     */
    public function generateAuthToken(UserInterface $user)
    {
        /** @var Provider $auth */
        $auth = $this->container->get('auth');

        return $auth->generateAuthToken($user);
    }

    /**
     * Generates a Request JWT Token use for SSO Authentication
     *
     * @param UserInterface $user
     *
     * @return string
     */
    public function generateRequestToken(UserInterface $user)
    {
        /** @var Provider $auth */
        $auth = $this->container->get('auth');

        return $auth->generateRequestToken($user);
    }

    /**
     * Sends a email with the reset password token
     *
     * @param $email
     * @param $reset_url
     */
    public function sendResetPasswordToken($email, $reset_url)
    {
        $this->validate(['email' => $email], ['email' => 'required|email']);

        /** @var Provider $auth */
        $auth = $this->container->get('auth');
        $user = $auth->findUserWithEmail($email);
        $resetToken = $auth->generateResetPasswordToken($user);

        // Storing the reset_token into password_reset_token to validate it.
        $userProvider = $auth->getUserProvider();
        $userProvider->update($user, [
            'password_reset_token' => $resetToken,
        ]);
        // Sending the project key in the query param makes sure the app will use the correct project
        // to send the new password to

        if ($reset_url) {
            $resetUrl = $reset_url . '?token=' . $resetToken;
        } else {
            $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, $newPassword)
    {
        if (!JWTUtils::isJWT($token)) {
            throw new InvalidResetPasswordTokenException($token);
        }

        if (JWTUtils::hasExpired($token)) {
            throw new ExpiredResetPasswordToken($token);
        }

        $payload = JWTUtils::getPayload($token);

        if (!JWTUtils::hasPayloadType(JWTUtils::TYPE_RESET_PASSWORD, $payload)) {

            throw new InvalidResetPasswordTokenException($token);
        }

        $auth = $this->getAuth();
        $auth->validatePayloadOrigin($payload);

        $userProvider = $auth->getUserProvider();
        $user = $userProvider->find($payload->id);

        if (!$user) {
            throw new UserNotFoundException();
        }

        // Throw invalid token if the payload email is not the same as the current user email
        if (!property_exists($payload, 'email') || $payload->email !== $user->getEmail()) {
            throw new InvalidResetPasswordTokenException($token);
        }

        if ($user->password_reset_token == null || $user->password_reset_token != $token) {
            throw new ExpiredResetPasswordToken($token);
        }

        $userProvider->update($user, [
            'password_reset_token' => null,
            'password' => $auth->hashPassword($newPassword)
        ]);
    }

    public function refreshToken($token)
    {
        $this->validate([
            'token' => $token
        ], [
            'token' => 'required'
        ]);

        /** @var Provider $auth */
        $auth = $this->container->get('auth');

        $payload = JWTUtils::getPayload($token);
        $userProvider = $auth->getUserProvider();
        $user = $userProvider->find($payload->id);

        /** @var UsersService $usersService */
        $usersService = new UsersService($this->container);

        $tfa_enforced = $usersService->has2FAEnforced($user->getId());

        if ($tfa_enforced && $user->get2FASecret() == null) {
            $new_token = $auth->refreshToken($token, true);
        } else {
            $new_token = $auth->refreshToken($token);
        }

        return ['data' => ['token' => $new_token]];
    }

    /**
     * Validates email+password+otp credentials
     *
     * @param $email
     * @param $password
     * @param $otp
     *
     * @throws UnprocessableEntityException
     */
    protected function validateCredentials($email, $password, $otp)
    {
        $payload = [
            'email' => $email,
            'password' => $password,
            'otp' => $otp
        ];
        $constraints = [
            'email' => 'required|string|email',
            'password' => 'required|string',
        ];

        // throws an exception if the constraints are not met
        $this->validate($payload, $constraints, self::AUTH_VALIDATION_ERROR_CODE);
    }
}