Jump to content

New Module: AppApi


Sebi

Recommended Posts

Thanks for the great modules. Maybe this was answered, unfortunately I didn't find anything about it.
How can I remove or overwrite the routes from "DefaultRoutes.php"? I have already tried it with this:

$module = $this->wire('modules')->get('AppAPI');
$module->registerRoute(
	'auth', [
		['OPTIONS', '', AppApiHelper::class, 'noEndPoint'],
		['GET', '', AppApiHelper::class, 'noEndPoint'],
		['POST', '', AppApiHelper::class, 'noEndPoint'],
		['DELETE', '', AppApiHelper::class, 'noEndPoint'],
	]
);

Unfortunately, they are still accessible and functional.

Link to comment
Share on other sites

On 6/18/2023 at 1:46 AM, Sebi said:

The overview page has a button to auto-generate an OpenAPI 3.0.3 json, that can be imported in tools like Postman or Swagger.

I have one more small suggestion to improve the function a bit ? 

Just replace the complete if inside the execute-endpoints.php file:

if ($action === 'action-get-openapi') {
[...]
}

With this:

if ($action === 'action-get-openapi') {
	header('Content-Type: application/json; charset=utf-8');
	echo json_encode($openApiOutput, JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE + JSON_UNESCAPED_LINE_TERMINATORS);
	die();
}

So Tracy is disabled on output and via the browser you can save the file directly as json.

 

And Thanks for this nice update ? @Sebi

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

  • 2 months later...

Hi @Sebi, I'm about to start work on a web-app that will (hopefully) use AppApi with 2-factor authentication, using the TOTP module from Ryan.
I would like to use the Double JWT authentication for the most secure set-up.

As I understand, this would currently not be possible, except maybe if I resort to PHP session instead and create my own login endpoint that implements some of the TfaTotp module methods. Is that correct?

Or do you have any pointers as to how to implement this with Double JWT?

Link to comment
Share on other sites

For Double JWT approach, have you considered sending tokens to client using httpOnly cookie?

I'm not an expert on this topic, but when researching to see where to save the tokens in my app, it seems the opinion for most secure involves httpOnly cookie (among other things).

One such conversation:
https://stackoverflow.com/questions/27067251/where-to-store-jwt-in-browser-how-to-protect-against-csrf?rq=1

Do you currently use localstorage on the browser? I'm creating a new app. Your module is awesome. But I'm wondering if localstorage is most secure on client? And how to do httpOnly cookie with this module?

Link to comment
Share on other sites

  • 2 weeks later...

This may be a silly question, but I couldn’t access the payload/body of a POST request within the public static function. The $data variable only contains the path, but not the body of the request. My code goes like this:

'email' => [
	['OPTIONS', '', ['POST']],
	['POST', '', Email::class, 'sendEmail']
],
class Email {
	public static function sendEmail ($data) {
		var_dump($data); // contains only path
		var_dump(wire('input')); // "post" data is null
	

My goal here is, of course, to send an email from a JavaScript application, using my PW installation as proxy.

Link to comment
Share on other sites

  • 2 weeks later...
On 9/7/2023 at 12:25 PM, eelkenet said:

Hi @Sebi, I'm about to start work on a web-app that will (hopefully) use AppApi with 2-factor authentication, using the TOTP module from Ryan.
I would like to use the Double JWT authentication for the most secure set-up.

I have moved away from the Double JWT solution for now, as PHP sessions seem to work fine for my application.
Also I managed to implement TFA validation, by editing the `classes/Auth.php` file.
That seemed to be the only way I could do so: I tried hooking into ___doLogin() but couldnt figure out the order of things. See below!

So, I replaced line 164:

$loggedIn = $this->wire('session')->login($user->name, $password);

With

// Add Tfa authentication through TfaTotp
if ($user->hasTfa()) {
    $tfa = $user->hasTfa(true);

    // Get code from incoming data
    // This only works if credentials + 2FA get sent inside the body
    $code = null;
    if (
        isset($data->tfacode) &&
        !empty('' . $data->tfacode)
    ) {
        $code = $data->tfacode;
    }

    $settings = $tfa->getUserSettings($user);
    if ($code && $tfa->isValidUserCode($user, $code, $settings)) {
        $loggedIn = $this->wire('session')->login($user->name, $password);
    } else {
        throw new AuthException("Invalid TFA code",  401);
    }
} else {
    $loggedIn = $this->wire('session')->login($user->name, $password);
}

And then added the 2FA in the body request:

// inside a log-in method

const credentials = {
  email: this.identification,
  password: this.password,
  tfacode: this.tfacode,
};

const response = await fetch('/api/auth', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'MY_API_KEY',
  },
  body: JSON.stringify(credentials),
});

if (response.ok) {
  return response.json();
} else {
  let error = await response.json();
  throw new Error(JSON.stringify(error));
}

I have also managed to do a proof of concept of a register route that works in combination with the TfaTotp module, but that needs some polishing.

 


Update: I figured out that, in order to get be able to hook into Auth::doLogin, I needed to check the 'Deactivate URL Hook' toggle in the AppApi module settings.
After doing so, I can now successfully hook and check for the Tfa, hurrah!

// In ready.php

wire()->addHookAfter('Auth::doLogin', function (HookEvent $event) {

  $log = wire('log');
  $session = wire('session');

  // Get the original return value of ___doLogin
  $originalReturnValue = $event->return;

  // Check if the original login was successful
  if (isset($originalReturnValue['username']) && !empty($originalReturnValue['username'])) {

    $username = $originalReturnValue['username'];
    $user = $event->wire('users')->get("name=$username");

    // Check if the user has TFA activated
    if ($user->hasTfa()) {

      $tfa = $user->hasTfa(true);

      // Get code from incoming data
      // This only works if 2FA gets sent inside the body, along with password and username

      $data = $event->arguments(0);
      $code = null;
      if (isset($data->tfacode) && !empty('' . $data->tfacode)) {
        $code = $data->tfacode;
      }

      $settings = $tfa->getUserSettings($user);
      if ($code && $tfa->isValidUserCode($user, $code, $settings)) {

        $log->save("tfaLogin", "Tfa code = correct!");
        $event->return = $originalReturnValue;
      }

      // Incorrect Tfa, sign out and throw 401
      else {

        $log->save("tfaLogin", "Tfa code = incorrect, do not allow logging in");
        $session->logout();

        throw new AuthException("Code incorrect!",  401);
      }
    }

    // No Tfa enabled, sign out and throw 401
    else {

      $log->save("tfaLogin", "User does not have Tfa enabled.. do not allow logging in!");
      $session->logout();

      throw new AuthException("TFA not enabled!",  401);
    }
  }
});

 

 

Link to comment
Share on other sites

  • 3 weeks later...

@Sebiwould you be open to a pull request that adds a new setting? Access Token Expires In?

I see that the access token uses the $config->sessionExpireInSeconds value for the access token expiration.

However, using JWT, I would like to have a shorter (60 minutes) session, because of using the Refresh Token to keep things logged in.

For the rest of my site (not using AppApi), the normal users I want to have 24 hour session, which I set with $config->sessionExpireInSeconds.

There is already an Expires At setting in AppApi, for Refresh Token. 
Is it okay to add a second Expires At setting for Access Token, in case we want to set a different expiration from rest of site?

Hope this makes sense?

Link to comment
Share on other sites

31 minutes ago, rastographics said:

@Sebiwould you be open to a pull request that adds a new setting? Access Token Expires In?

I see that the access token uses the $config->sessionExpireInSeconds value for the access token expiration.

However, using JWT, I would like to have a shorter (60 minutes) session, because of using the Refresh Token to keep things logged in.

For the rest of my site (not using AppApi), the normal users I want to have 24 hour session, which I set with $config->sessionExpireInSeconds.

There is already an Expires At setting in AppApi, for Refresh Token. 
Is it okay to add a second Expires At setting for Access Token, in case we want to set a different expiration from rest of site?

Hope this makes sense?

For now I'm doing this on line 271 of AppApi/classes/Auth.php . But  I have to be careful when updating the module now.

 

	/* ADDED TO GIVE JWT ACCESS TOKEN A DIFFERENT SESSION LENGTH THAN REGULAR SITE LOGINS  */
		$config = $this->wire('config');
		$expireSeconds = $config->sessionExpireSeconds;
		if(isset($config->appApiSessionExpireSeconds)){
			$expireSeconds = $config->appApiSessionExpireSeconds;
		} 
		$apptoken->setExpirationTime(time() + $expireSeconds);

 

Link to comment
Share on other sites

  • 4 months later...

Hallo, I have some problems with special characters in POST URLs. It’s awkward. These are my routes:

['OPTIONS', 'tasks/{city}/{employee}', ['GET']],
['GET', 'tasks/{city}/{employee}', Googledocs::class, 'fetchTasks'],
['OPTIONS', 'tasks/{city}/{employee}/{row}', ['POST']],
['POST', 'tasks/{city}/{employee}/{row}', Googledocs::class, 'putTasksRow']

Employees with an Umlaut, however, seem to confuse the path/method detection:

(Yes, I should be using PUT or PATCH instead of POST, but my hosting provider has – strangely enough – a problem with Umlauts as well, when they are in PUT requests. It doesn’t even start PHP in these cases and throws an 405 error from Apache)

 

Link to comment
Share on other sites

  • 1 month later...

@Sebi Right now, it is possible to view the OpenAPI json by going to /admin/setup/appapi/endpoints/action-get-openapi/

I want to automate building html-based documentation with Redocly CLI based on that JSON data, however because how it is currently programmed, I can't use a script to cleanly grab that JSON nor is there a method that easily gets it in that format given how the code is structured (using the executeEndpoints method doesn't get the JSON in the same way and it relies on urlSegments).

Is it possible to refactor the code / create a new method that would allow getting the OpenAPI JSON directly?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...