Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions cpanfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
requires "Class::Method::Modifiers" => "0";
requires "Crypt::Passphrase" => "0.023";
requires "Crypt::Passphrase::Argon2" => "0.010";
requires "Digest" => "0";
requires "Exporter" => "0";
requires "File::Spec::Functions" => "0";
Expand Down
2 changes: 2 additions & 0 deletions dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ Mojolicious::Plugin::I18N = 1.6 ; Must be all-caps I18N!
Role::Tiny = 2.000001 ; Required for Mojolicious roles
Class::Method::Modifiers = 0 ; Required for around/before/after
JSON::Validator = 5.00
Crypt::Passphrase = 0.023 ; Password hashing
Crypt::Passphrase::Argon2 = 0.010 ; Default password hash
Sys::Hostname = 0
File::Spec::Functions = 0
FindBin = 0
Expand Down
2 changes: 2 additions & 0 deletions eg/meet-space/myapp.pl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
plugin 'Yancy::Plugin::Editor', {
model => $model,
storage => $storage,
# Development app: Allow editing without logging in
allow_anonymous => 1,
};
### /Mojolicious::Plugin::Yancy

Expand Down
31 changes: 20 additions & 11 deletions lib/Mojolicious/Plugin/Yancy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ By default, the L<Yancy::Plugin::Form::Bootstrap4> form plugin is
loaded. You can override this with your own form plugin. See
L<Yancy::Plugin::Form> for more information.

=head2 yancy.file
=head2 File storage

By default, the L<Yancy::Plugin::File> plugin is loaded to handle file
uploading and file management. The default path for file uploads is
C<$MOJO_HOME/public/uploads>. You can override this with your own file
plugin. See L<Yancy::Plugin::File> for more information.
By default, the editor stores uploaded files in a L<Yancy::Storage>
rooted at C<$MOJO_HOME/public/uploads>. You can override this with
C<< editor => { storage => ... } >>. See L<Yancy::Storage> for more
information.

=head2 yancy.plugin

Expand Down Expand Up @@ -165,6 +165,7 @@ use Mojo::File qw( path );
use Mojo::Loader qw( load_class );
use Yancy::Util qw( load_backend curry copy_inline_refs is_type json_validator );
use Yancy::Model;
use Yancy::Storage;
use Storable qw( dclone );
use Scalar::Util qw( blessed );

Expand Down Expand Up @@ -215,15 +216,23 @@ sub register {
# override this one
# XXX: Form and File should both be part of the content plugin
$app->plugin( 'Yancy::Plugin::Form::Bootstrap4' );
$app->plugin( 'Yancy::Plugin::File' => {
path => $app->home->child( 'public/uploads' ),
} );

# Add the default editor unless the user explicitly disables it
if ( !exists $config->{editor} || defined $config->{editor} ) {
$app->plugin( 'Yancy::Plugin::Editor' => {
%{ $config->{editor} // {} },
} );
my $editor = $config->{editor} // {};
# The editor must be told how to check logins before it is added
if ( !$editor->{users} && !$editor->{allow_anonymous} ) {
$app->log->warn(
'Yancy editor not loaded: Set `editor => { users => $user_model }` to require logins,'
. ' or `editor => { allow_anonymous => 1 }` to allow anyone to edit',
);
}
else {
$app->plugin( 'Yancy::Plugin::Editor' => {
storage => Yancy::Storage->new( $app->home->child( 'public/uploads' ) ),
%$editor,
} );
}
}
}

Expand Down
71 changes: 48 additions & 23 deletions lib/Yancy/Auth.pm
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,25 @@ sub new($class, $app, @args) {
push @{$app->renderer->paths}, "$templates";
}

# Auth templates use the `l` helper for translated strings
if (!$app->renderer->get_helper('l')) {
$app->plugin('I18N', { namespace => 'Yancy::I18N' });
}

# TODO: Auth sets up routes in $app

# Add a logout route for the user model, unless another auth already did
if ( !$app->routes->find('auth.logout') ) {
my $model = $self->model;
$app->routes->any( [qw( GET POST )] => '/auth/logout' )->to( cb => sub ($c) {
$model->end_session( $c );
my $to = $c->req->param( 'return_to' );
$to = '/' unless $self->is_local_path( $to );
$c->res->headers->location( $to );
return $c->rendered( 303 );
} )->name('auth.logout');
}

$self->model->add_auth( $self );
return $self;
}
Expand All @@ -48,43 +65,51 @@ sub form_for( $self, $c, $login ) {
# If $login is provided, do not make the user type it in again.
}

=method is_local_path

my $ok = $auth->is_local_path( $url );

Returns true if the given URL is a path on this site that is safe to redirect
to. It must start with a single C</>, and must not contain backslashes or
control characters, which browsers may treat as a C<//host> URL.
L<http://cwe.mitre.org/data/definitions/601.html>

=cut

sub is_local_path( $self, $url ) {
return defined $url
&& $url =~ m{^/(?![/\\])}
&& $url !~ m{[\\\x00-\x1f\x7f]};
}

sub return_to( $self, $c ) {
my $return_to
my $referrer = $c->req->headers->referrer;
my ( $return_to ) = grep { $self->is_local_path( $_ ) } (
# If we've specified one, go there directly
= $c->req->param( 'return_to' )
? $c->req->param( 'return_to' )
$c->req->param( 'return_to' ),
# Check flash storage, perhaps from a redirect to the login form
: $c->flash('return_to') ? $c->flash('return_to')
$c->flash('return_to'),
# If this is the login page, go back to referer
: $c->current_route =~ /^auth\./
&& $c->req->headers->referrer
&& $c->req->headers->referrer !~ m{^(?:\w+:|//)}
? $c->req->headers->referrer
( ( $c->current_route // '' ) =~ /^auth\./ ? $referrer : () ),
# Otherwise, return the user here
: ( $c->req->url->path || '/' )
;
if ( $return_to =~ m{^(?:\w+:|//)} ) {
die q{`return_to` can not contain URL scheme or host},
}
return $return_to;
$c->req->url->path->to_string,
);
return $return_to // '/';
}

sub start_session( $self, $c, $login ) {
# Start a session for the user
# XXX: Set "current user" in session
# XXX: Set last login datetime
# XXX: Set last used provider
# XXX: Set cookie to make auth easier next time

my $to = $c->req->param( 'return_to' ) || '/';

# Do not allow return_to to redirect the user to another site.
# http://cwe.mitre.org/data/definitions/601.html
if ( $to =~ m{^(?:\w+:|//)} ) {
return $c->reply->exception(
q{`return_to` can not contain URL scheme or host},
);
}
my $to = $c->req->param( 'return_to' );
$to = '/' unless $self->is_local_path( $to );

my $user = $self->get_user( $login )
// die qq{Can not start session for unknown user "$login"\n};
$self->model->start_session( $c, $user->{user_id} );

$c->res->headers->location( $to );
return $c->rendered( 303 );
Expand Down
Loading
Loading