MIN_PERL_VERSION => '5.14.0',
LICENSE => 'AGPL_3',
SIGN => 1,
+ clean => {
+ FILES => 'static/css/ static/js.js'
+ },
BUILD_REQUIRES => {
- qw/Test::MockTime 0
+ qw/CSS::Minifier::XS 0
+ File::Slurp 0
+ JavaScript::Minifier::XS 0
+ Test::MockTime 0
Test::More 0
Test::WWW::Mechanize::PSGI 0/,
},
PREREQ_PM => {
qw/Carp 0
- Digest::SHA 0
Encode 0
List::Util 0
POSIX 0
strict 0
warnings 0
- CSS::Minifier::XS 0
- Email::Simple 0
- Email::Sender::Simple 0
- File::Slurp 0
- Gruntmaster::Data 0
- HTML::Seamstress 0
- JavaScript::Minifier::XS 0
- JSON::MaybeXS 0
- Log::Log4perl 0
- PerlX::Maybe 0
- Plack::Builder 0
- Plack::Request 0
- Plack::Util 0
- Scope::Upper 0
- Tie::Hash::Expire 0
- Web::Simple 0.019/,
+ Email::Simple 0
+ Email::Sender::Simple 0
+ File::Slurp 0
+ Gruntmaster::Data 0
+ HTML::Seamstress 0
+ JSON::MaybeXS 0
+ Log::Log4perl 0
+ PerlX::Maybe 0
+ Plack::Builder 0
+ Plack::Middleware::Auth::Complex 0
+ Plack::Util 0
+ Scope::Upper 0
+ Web::Simple 0.019/,
},
META_MERGE => {
dynamic_config => 0,
<h1>Reset password</h1>
<form action="/action/reset" method="POST" class="jsform" role="form">
<div class="form-group"><label for="rst_username">Username</label><input type="text" id="rst_username" name="username" class="form-control" required></div>
-<div class="form-group"><label for="rst_password">New password</label><input type="password" id="rst_password" name="password" class="form-control" required></div>
+<div class="form-group"><label for="rst_new_password">New password</label><input type="password" id="rst_new_password" name="new_password" class="form-control" required></div>
+<div class="form-group"><label for="rst_confirm_new_password">Confirm new password</label><input type="password" id="rst_confirm_new_password" name="confirm_new_password" class="form-control" required></div>
<div class="form-group"><label for="rst_token">Reset token</label><input type="text" id="rst_token" name="token" class="form-control" required></div>
<input type="submit" class="btn btn-default" value="Reset password">
</form>
use Gruntmaster::Data;
use Plack::App::Gruntmaster;
use Plack::Builder;
-use Plack::Request;
use Plack::Util;
-use Digest::SHA qw/sha256/;
use Log::Log4perl;
-use Tie::Hash::Expire;
use constant AUTH_TIMEOUT => 5 * 60;
use constant ACCESSLOG_FORMAT => '%{X-Forwarded-For}i %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"';
frame-ancestors 'none'
img-src 'self' https://static.mindcoding.ro https://www.google-analytics.com/collect
referrer origin-when-cross-origin
-script-src https://static.mindcoding.ro/js.js https://www.google-analytics.com/analytics.js
-style-src https://static.mindcoding.ro/css/
+script-src https://static.mindcoding.ro/static/js.js https://www.google-analytics.com/analytics.js
+style-src https://static.mindcoding.ro/static/css/
CSP
chomp $csp;
$csp =~ s/\n/; /gr;
my $db;
-tie my %auth, 'Tie::Hash::Expire', {expire_seconds => AUTH_TIMEOUT};
-
-sub authenticate {
- my ($user, $pass, $env) = @_;
- my $key = sha256 "$user:$pass";
- $env->{'gruntmaster.user'} = $user;
- return 1 if exists $auth{$key};
- return unless $db->user($user) && $db->user($user)->check_passphrase($pass);
- $auth{key} = 1;
-}
-
sub add_database {
my $app = $_[0];
sub {
enable 'Static', path => qr,^/static/,;
enable 'Log4perl', category => 'plack';
enable \&add_database;
- enable_if { shift->{HTTP_AUTHORIZATION} } 'Auth::Basic', authenticator => \&authenticate, realm => 'Gruntmaster 6000';
+ enable '+Plack::App::Gruntmaster::Auth',
+ dbi_connect => [$ENV{GRUNTMASTER_DSN} // 'dbi:Pg:', '', ''],
+ realm => 'Gruntmaster 6000',
+ mail_from => $ENV{GRUNTMASTER_RESET_FROM};
Plack::App::Gruntmaster->run_if_script
}
(function(){
'use strict';
function set_style(name){
- $('#stylesheet').attr("href", "https://static.mindcoding.ro/css/" + name + ".css");
+ $('#stylesheet').attr("href", "https://static.mindcoding.ro/static/css/" + name + ".css");
localStorage.setItem("theme", name);
$(document).ready(function() {
if(name == 'slate' || name == 'cyborg')
use strict;
our $VERSION = '5999.000_001';
-use CSS::Minifier::XS;
use Encode qw/encode decode/;
use File::Slurp qw/read_file/;
-use JavaScript::Minifier::XS;
use JSON::MaybeXS qw/encode_json/;
use PerlX::Maybe;
use Scope::Upper qw/unwind SUB UP/;
};
use constant NOT_FOUND => [404, ['X-Forever' => 1, 'Content-Type' => 'text/plain'], ['Not found']];
-use constant FORBIDDEN => [401, ['Content-Type' => 'text/plain', 'WWW-Authenticate' => 'Basic realm="Gruntmaster 6000"'], ['Forbidden']];
-
-sub development() { ($ENV{PLACK_ENV} // 'development') eq 'development' }
my ($env, $privacy);
sub db { $env->{'gruntmaster.dbic'} }
sub remote_user {
- my $user = $env->{'gruntmaster.user'};
+ my $user = $env->{REMOTE_USER};
$user &&= db->user($user);
$user
}
my ($condition) = @_;
$privacy = 'private' if $condition;
return if !$condition || admin;
- unwind FORBIDDEN, SUB UP
+ unwind $env->{authcomplex}->unauthorized, SUB UP
}
sub dispatch_request{
$privacy = 'public';
sub (GET) {
- sub (/css/:theme) {
- my $theme = $_{theme};
- return NOT_FOUND unless -e "css/themes/$theme.css";
- my $css = read_file "css/themes/$theme.css";
- $css .= read_file $_ for <css/*.css>;
- my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'text/css; charset=utf-8');
- [200, \@headers, [development ? $css : CSS::Minifier::XS::minify $css]]
- },
-
- sub (/js.js) {
- my $js;
- $js .= read_file $_ for <js/*.js>;
- my @headers = ('X-Forever' => 1, 'Cache-Control' => 'public, max-age=604800', 'Content-Type' => 'application/javascript; charset=utf-8');
- [200, \@headers, [development ? $js : JavaScript::Minifier::XS::minify $js]]
- },
-
sub (/robots.txt) { NOT_FOUND },
sub (/src/:job) {
},
sub (POST) {
- sub (/action/register + %:username=&:password=&:confirm_password=&:name=&:email=&:phone=&:town=&:university=&:country=&:level=) {
- return reply 'Parameter too long' if grep { length > 200 } values %_;
- return reply 'Bad username. Allowed characters are letters, digits and underscores, and the username must be between 2 and 20 characters long.' unless $_{username} =~ USER_REGEX;
- return reply 'Username already in use' if db->user($_{username});
- return reply 'The two passwords do not match' unless $_{password} eq $_{confirm_password};
-
- db->users->create({id => $_{username}, name => $_{name}, email => $_{email}, phone => $_{phone}, town => $_{town}, university => $_{university}, country => $_{country}, level => $_{level}});
- db->user($_{username})->set_passphrase($_{password});
-
- reply 'Registered successfully';
- },
-
- sub (/action/passwd + %:password=&:new_password=&:confirm_new_password=) {
- forbid !remote_user;
- return reply 'Incorrect password' unless remote_user->check_passphrase($_{password});
- return reply 'The two passwords do not match' unless $_{new_password} eq $_{confirm_new_password};
- remote_user->set_passphrase($_{new_password});
- reply 'Password changed successfully';
- },
-
sub (/action/submit + %:problem=&:contest~&:prog_format=&:source_code~ + *prog~) {
my (undef, undef, $prog) = @_;
forbid !remote_user;
[303, [Location => '/log/' . $newjob->id], []]
},
-
- sub (/action/request-reset + %:username=) {
- return reply 'Password resets are disabled' unless $ENV{GRUNTMASTER_RESET_FROM};
- my $user = db->user($_{username});
- return reply 'No such user' unless $user;
- my $token = join ':', $user->make_reset_hmac;
- my $body = <<EOF;
-Someone has requested a password reset for your account.
-
-To reset your password, please submit the reset password form on the
-website using the following information:
-
-Username: $_{username}
-Password: <your new password>
-Reset token: $token
-
-The token is valid for 24 hours.
-EOF
- my $email = Email::Simple->create(
- header => [
- From => $ENV{GRUNTMASTER_RESET_FROM},
- To => $user->email,
- Subject => 'Password reset token',
- ],
- body => $body,
- );
-
- my $ok = 0;
- eval {
- sendmail $email;
- $ok = 1;
- };
- return reply 'Email sent' if $ok;
- reply "Failure sending email: $@";
- },
-
- sub (/action/reset + %:username=&:password=&:token=) {
- my $user = db->user($_{username});
- return reply 'No such user' unless $user;
- my ($token, $exp) = split ':', $_{token};
- return reply 'Reset token is expired' if time >= $exp;
- return reply 'Bad reset token' unless $user->make_reset_hmac($exp) eq $token;
- $user->set_passphrase($_{password});
- reply 'Password reset successfully';
- },
}
}
--- /dev/null
+package Plack::App::Gruntmaster::Auth;
+
+use 5.014000;
+use strict;
+our $VERSION = '5999.000_001';
+
+use parent qw/Plack::Middleware::Auth::Complex/;
+
+sub call_register {
+ my ($self, $req) = @_;
+ return $self->bad_request('Parameter too long') if grep { length > 100 } $req->parameters->values;
+
+ $self->SUPER::call_register($req);
+}
+
+sub create_user {
+ my ($self, $parms) = @_;
+ my %parms = $parms->flatten;
+ my $sth = $self->{dbh}->prepare_cached('INSERT INTO users (id, name, email, phone, town, university, country, level, passphrase) VALUES (?,?,?,?,?,?,?,?,?)');
+ $sth->execute(@parms{qw/username name email phone town university country level/}, $self->hash_passphrase($parms{password}));
+}
+
+1;
+__END__
--- /dev/null
+#!/usr/bin/perl
+use v5.14;
+use warnings;
+
+use CSS::Minifier::XS qw//;
+use JavaScript::Minifier::XS qw//;
+
+use File::Slurp qw/read_file write_file/;
+
+mkdir 'static';
+mkdir 'static/css';
+
+my $common_css;
+$common_css .= read_file $_ for <css/*.css>;
+for (<css/themes/*>) {
+ my ($theme) = m,themes/(.*)\.css,;
+ my $css = read_file $_;
+ $css .= $common_css;
+ write_file "static/css/$theme.css", CSS::Minifier::XS::minify $css;
+}
+
+my $js;
+$js .= read_file $_ for <js/*.js>;
+write_file 'static/js.js', JavaScript::Minifier::XS::minify $js;
+++ /dev/null
-html,body{
- background-color: black;
- color: white;
-}
-
-div#title,div#subtitle{
- font-family: "Liberation Mono","DejaVu Sans Mono","Lucida Console",monospace;
- font-weight: bold;
- text-align: center;
- padding: 0;
- margin: 0;
-}
-
-div#title{
- font-size: 7em;
-}
-
-div#subtitle{
- font-size: 3em;
-}
-
-div#result{
- padding: 0.5em;
- text-align: center;
-/* border: thin dotted white; */
-}
-
-textarea{
- width: 100%;
- height: 10em;
- display: block;
-}
-
-table,td,th{
- text-align: center;
- border-collapse: collapse;
- border: thin dotted white;
- padding: 0.1em;
-}
-
-table{
- width: 98%;
- margin: 1%
-}
-
-table.upsstats{
- width: auto;
- margin: 1em auto;
-}
-
-table.upsstats table, table.upsstats table tr, table.upsstats table th, table.upsstats table td {
- border-width: 0;
- width: auto;
-}
-
-a:link,a:visited{
- color: white;
-}
-
-table.alternating>tbody>tr:nth-child(odd){
- background-color: #222;
-}
-
-span.i{
- color: #F00;
-}
-
-span.Eval{
- color: #0F0;
-}
-
-div.centertext{
- text-align: center;
-}
-
-h1.nomargintop{
- margin-top: 0;
-}
-
-span.yes{
- color: #0F0;
-}
-
-span.no{
- color: #F00;
-}
-
-span.lowyes{
- color: #8F8;
-}
-
-td.size{
- color: #EEE;
- font-size: 0.9em;
-}
-
-ul.stats{
- margin:0;
- padding:0;
-}
-
-ul.stats li{
- display: inline-block;
- margin-right: 1em;
-}
-
-a.nounderline:link{
- text-decoration: none;
-}
-
-td.r0{
- color: green;
-}
-
-td.r10{
- color: gold;
-}
-
-td.r1{
- color: darkred;
-}
-
-td.r3{
- color: aqua;
-}
-
-td.r-1{
- color: red;
-}
-
-table.sample, table.sample th, tample.sample td{
- width: auto;
- vertical-align: top;
- margin: 0;
- padding: 0.2em 1em;
- text-align: left !important;
-}
-
-nav ul{
- list-style: none;
- padding: 0;
- text-align: center;
-}
-
-nav li a{
- padding: 0.5em;
- display: inline-block;
- text-decoration: none;
-}
-
-nav li{
- margin: 0.5em;
- background-color: #222;
- display: inline-block;
- border: thin dotted white;
-}
-
-nav li:hover{
- background-color: #444;
-}
-
-footer{
- width: 60em;
- margin: auto;
- margin-top: 6em;
- white-space: pre-wrap;
- font-size: 75%;
- color: #777;
-}
-
-a.disabled{
- pointer-events: none;
- text-decoration: none;
-}
-
-#admin{
- font-size: 75%;
- color: #777;
- position: absolute;
- top: 1px;
- right: 1px;
-}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="https://static.mindcoding.ro/css/slate.css" id="stylesheet">
-<script src="https://static.mindcoding.ro/js.js" type="text/javascript" async defer></script>
+<link rel="stylesheet" href="https://static.mindcoding.ro/static/css/cyborg.css" id="stylesheet">
+<script src="https://static.mindcoding.ro/static/js.js" type="text/javascript" async defer></script>
<body>
<nav role="navigation">