use 5.34.0; package Routes; use Dancer2; use Dancer2::Plugin::Deferred; use Dancer2::Plugin::Auth::Tiny; use Dancer2::Plugin::CryptPassphrase; # this is (deliberately) slow due to hashing use IO::All; use Data::Printer; use constant PERLVERSION => $^V; use if PERLVERSION lt v5.36.0, experimental => 'signatures'; # but not for routes set auto_page => 1; # maybe not in use, but useful anyway use App::Model; my $app = App::Model->new( cfg => config() ); # p $app; our $VERSION = $app->version; say 'App version: '.$VERSION; my $docs_path = path( config->{appdir}, 'documents' ); # p $docs_path; hook before => sub { }; hook before_template_render => sub { my $tokens = shift; # $tokens->{env} = p %ENV; $tokens->{app_version} = $VERSION; }; get '/' => needs login => sub { # my $s = session; p $s; template home => { records => query_parameters->get('records') }; }; # for redirect after successful submit: get '/id/:id[Int]' => sub { my $id = route_parameters->get('id'); my $rec = $app->get_document($id); # p $rec; # AoH template home => { records => $rec }; }; get '/login' => sub { # my $p = params(); p $p; my $return_url = query_parameters->get('return_url'); # set by Auth::Tiny template login => { return_url => $return_url } }; post '/login' => sub { # my $p = params(); p $p; my $password = body_parameters->get('password'); # get return_url (not used for this app): my $return_url = body_parameters->get('return_url'); # say $return_url; if ( my $username = _authenticate_user($password) ) { app->change_session_id; session user => $username; # Plugin::Auth::Tiny requires session id = 'user' info "$username successfully logged in"; redirect $return_url || '/'; } else { $username = body_parameters->get('username') || 'unknown user'; warning "failed login attempt for $username"; template login => { login_error => 1, return_url => $return_url }; } }; post '/service_record' => needs login => sub { my $params = request->parameters->as_hashref; # p $params; my $data_file = upload('filename'); # p $data_file; # optional if ( $data_file ) { my $filename = $data_file->filename; # generate $filepath from docs_path & filename, create var of same name: my $filepath = var filepath => path($docs_path, $filename); # p $filepath; # check if it exists and bail if it does: if ( -e $filepath ) { var input_error => qq!file "$filename" already exists!; forward '/', { records => [$params] }, { method => 'GET' }; # tt needs AoH } # add filename to params: $params->{filename} = $filename; # say vars->{filepath}; } # p $params; # if update, get document_id as a param, captured in /update/:document_id # route and forwarded as a query param: $params->{id} = query_parameters->get('document_id'); # null unless edit my $res = $app->save_document($params); # p $res; if ( $res->{error} ) { var input_error => $res->{error}; forward '/', { records => [$params] }, { method => 'GET' }; } else { deferred input_success => 1; # doesn't need message # upload file to docs dir if uploaded: $data_file->copy_to( vars->{filepath} ) if $data_file; redirect uri_for '/id/' . $res->{id}; } }; get '/gitlog' => sub { template gitlog => { log => $app->gitlog } }; # this isn't needed except for testing: get '/logout' => sub { app->destroy_session; redirect '/' }; get '/dancr' => sub { template index => {}, { layout => 'default' } }; # using PBKDF2 for development, faster but less secure than Argon2 for deployment sub _authenticate_user ($password) { my $username = body_parameters->get('username'); if ( grep app->environment eq $_, qw/development test/ ) { # use faster PBKDF2 method my $user = setting 'user'; # in development_local.yml $username ||= $user->{name}; # don't need to provide it for dev return $username if $app->verify_password( $password, $user->{pwd} ); } else { # Plugin::CryptPassphrase (uses more secure argon2 auth) my $user = $app->find_user($username); return $username if verify_password( $password, $user->{password} ); } return undef; # failed authentication } true;