
=head1 NAME

Mojolicious::Plugin::Loco - launch local GUI via default web browser

=head1 VERSION

version 0.003

=head1 SYNOPSIS

  use Mojolicious::Lite;
  plugin 'Loco';

  get '/' => "index";

  post '/kick-me' => sub {
    my $c = shift;
    return if $c->loco->csrf_fail;
    # system q(cat /dev/urandom | fdisk);
    $c->flash(msg => 'Ok, that hurt.');
    $c->redirect_to('/');
  };

  post '/quit' => sub { shift->loco->quit; };

  app->start;

  __DATA__
  @@ index.html.ep
  % layout 'default';
  <h1>Ready!</h1>
  %= csrf_button_to "Kick Me", '/kick-me', method => 'POST';
  %= csrf_button_to "Exit",    '/quit',    method => 'POST';
  <p><%= $c->flash('msg')%></p>

  @@ layouts/default.html.ep
  <!DOCTYPE html><html><head>
  %= $c->loco->jsload;    # needs to be on every page
  %= content_for 'head';
  </head><body>
  %= content
  </body>
  </html>

=head1 DESCRIPTION

This plugin allows writing low-effort desktop applications using L<Mojolicious> (even cross-platform ones if your code is sufficiently portable).

When run as a daemon listening on an explicit loopback/localhost port, the plugin causes a dedicated window to open in your default internet browser, assuming that we're in a desktop/window-manager context where an internet browser application is installed.  The server will then shut down once the browser window and all descendants thereof are subsequently closed.

For applications that will be handling sensitive data or that will be making changes to the filesystem or other local machine state, please see L</"SECURITY CONSIDERATIONS">

This module is currently experimental; the API may change without notice.

=head1 OPTIONS

=head2 initial_wait

How many seconds to wait on server start for browser window to finish loading.  Defaults to 10.

=head2 final_wait

How many seconds to wait after browser window ceases communicating before terminating (we do not rely on C<window.unload>).  Defaults to 3.  Settable from L</loco.conf>.

Since javascript timer events from backgrounded/hidden tabs/windows are typically throttled, reducing this below 2 will most likely make the application prematurely terminate whenever the window is hidden or minimized.

=head2 entry

URI path for the entry point of your application (i.e., what to display when the brower window opens).  Defaults to C</>.

=head2 api_path

Path prefix for URIs used by this plugin.  This is where the various endpoints needed by this module live (and is also where the required javascript file(s) are served from).  Defaults to C</hb/>; it can be pretty much anything as long as it's distinct from what the rest of your application uses.  But it would be best to keep it short.

=head2 allow_other_browsers

If false (default), L<$c-E<gt>validation-E<gt>csrf_protect|Mojolicious::Validator::Validation#csrf_protect> rejects all C<csrf_token> values other than the one shared between the server and the initial browser launched.  Requests that check for this are then blocked from all other sessions, i.e., those created by other local browsers/processes (or remote processes if the listening point is network accessible).  Setting this true disables this extra protection and L<$c-E<gt>validation-E<gt>csrf_protect|Mojolicious::Validator::Validation#csrf_protect> will behave in the default manner.

Read L</"SECURITY CONSIDERATIONS"> if you think you might want to set this.

=head1 HELPERS

=head2 loco->conf

Get or set plugin configuration values.  Only some values are actually settable.

  $c->loco->conf                       # -> full hash of settings
  $c->loco->conf('final_wait')         # -> 3
  $c->loco->conf(final_wait => 200)    # -> $c, sets final_wait
  $c->loco->conf({final_wait => 200})  # -> same

=head2 loco->csrf_fail

Performs the C<csrf_token> validation; returns true and renders an an HTTP 400 reply if it fails, otherwise returns false.  If L</allow_other_browsers> is false, this includes the "same browser" check.

  return if $c->loco->csrf_fail;

=head2 loco->quit

Stop the server and render a final page if necessary.  This includes the C<csrf_token> check.

=head2 loco->jsload

Loads whatever javascript needs to be in the <head> section of every page to be displayed in the browser window.  You most likely want this in your default layout.

  %= $c->loco->jsload;

Or you can be more elaborate

  %= $c->loco->jsload( jquery => 'https://code.jquery.com/jquery-3.3.1.min.js', begin
        .on_hb(function(h) {
          // do something on every heartbeat
	  $('#heartbeat').html(h);
        })
  % end );

Options for L</jsload> include

=head3 jquery

URL to be loading jquery from.  Default is to use the jquery version included in the Mojolicious distribution.  Specifying an empty string C<''> suppresses jquery loading entirely, if you have already loaded it as part of some other package.

=head3 nofinish

Suppress the default C<on_finish> handler.

=head3 begin

Final C<begin> block, if provided, will be assumed to be javascript code to further configure the heartbeat object (code is preceded by C<$().heartbeat()>), typically to add C<on_hb> or C<on_finish> handlers

=head1 SECURITY CONSIDERATIONS

In a typical desktop application, traffic between the user interface and the application code will be invisible to remote attackers and code running in different processes.  Structuring your application as a Mojolicious server with UI provided by an internet browser using this plugin will, in some cases, expose this traffic and make additional interferences possible.  

What follows is a (necessarily incomplete) listing of vulnerabilities that can be introduced.

=over

=item Listening on Network-accessible Interfaces/Ports

It is stongly recommended that you B<not> use a listening point that is network accessible, i.e., use either a loopback address, UNIX socket (if available), unless you have a reliable firewall to block incoming connection attempts from untrusted sites.  Note that the listening point for Mojolicious servers is the value of C<-l> argument passed to the C<daemon> command, or the value of C<$ENV{MOJO_LISTEN}> if set, or C<localhost:3000> by default, so listening on a network-accessible interface should not happen unless you explicitly call for it.

You should B<especially not> use a remotely accessible listing point if you have set L</allow_other_browsers>.

=item Cross-Site Scripting

We assume the use of a modern browser that makes HTML page content and cookies unobservable from scripts running in other domains.  However since the style sheets and script pages served by your application will be arbitrarily accessible cross-domain, you should ensure that they do not reveal any application secrets (e.g., the current C<csrf_token>).

Also, your pages should not reference or include script code from untrusted sources.

=item Cross-Site Request Forgery

Since any webpage may contain links, buttons, or forms targeting your application, you should use L<$c-E<gt>validation-E<gt>csrf_protect|Mojolicious::Validator::Validation#csrf_protect> on any requests that change application state, write to the local filesystem, or otherwise make use of privileges beyond those typically available in a browser scripting context.  

=item Access From Other Local Processes

With this plugin loaded, C<< $c->validation->csrf_protect >> is restricted to I<only> allowing requests from the session created by the browser invoked by the plugin.

If, however you choose to set L</allow_other_browsers> then your application is potentially vulnerable to requests from other processes (including other browsers) running on the local host, which may then make their own connections to the listening port and obtain usable sessions.  This will be of special concern if, e.g.,

=over

=item *

You are concurrently running instances of alternative or out-dated browsers visiting untrusted sites able to exploit cross-site scripting vulnerabiliities to obtain the contents of your application's HTML pages or cookie values.

=item *

There are other local users on your machine.

=item *

Your application runs setuid or otherwise invokes elevated privileges (which could then be used by other processes to bypass various protections).

=back

=back

=head1 METHODS

L<Mojolicious::Plugin::Loco> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 register

  $plugin->register(Mojolicious->new, entry => '/');

Register plugin in L<Mojolicious> application.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.

=head1 AUTHOR

Roger Crew <wrog@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2018 by Roger Crew.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
