Showing 9 changed files with 306 additions and 177 deletions
+5
gitprep.conf
... ...
@@ -6,6 +6,11 @@
6 6
 ;git_bin=/usr/local/bin/git
7 7
 ;git_bin=~/local/git
8 8
 
9
+[admin]
10
+;;; If you forget admin password,
11
+;;; set this value to 1 and access /reset-password page.
12
+;;; Forget to comment out after resetting password.
13
+;reset_password=1
9 14
 
10 15
 [hypnotoad]
11 16
 listen=http://*:10020
+3
lib/Gitprep.pm
... ...
@@ -150,6 +150,9 @@ EOS
150 150
   # Auto route
151 151
   $self->plugin('AutoRoute');
152 152
   
153
+  # Reset admin password
154
+  
155
+  
153 156
   # User defined Routes
154 157
   {
155 158
     my $r = $self->routes->route->to('main#');
+17
lib/Gitprep/API.pm
... ...
@@ -5,12 +5,29 @@ use Carp ();
5 5
 use File::Basename ();
6 6
 use Mojo::JSON;
7 7
 use Encode qw/encode decode/;
8
+use Digest::MD5 'md5_hex';
8 9
 
9 10
 sub croak { Carp::croak(@_) }
10 11
 sub dirname { File::Basename::dirname(@_) }
11 12
 
12 13
 has 'cntl';
13 14
 
15
+sub encrypt_password {
16
+  my ($self, $password) = @_;
17
+  
18
+  my $salt;
19
+  $salt .= int(rand 10) for (1 .. 40);
20
+  my $password_encryped = md5_hex md5_hex "$salt$password";
21
+  
22
+  return ($password_encryped, $salt);
23
+}
24
+
25
+sub check_password {
26
+  my ($self, $password, $salt, $password_encryped) = @_;
27
+  
28
+  return md5_hex md5_hex "$salt$password" eq $password_encryped;
29
+}
30
+
14 31
 sub new {
15 32
   my ($class, $cntl) = @_;
16 33
 
+12 -18
templates/auto/_admin/user/create.html.ep
... ...
@@ -1,21 +1,12 @@
1 1
 <%
2
-  use Mojo::Util 'md5_sum';
3
-  
4 2
   my $api = gitprep_api;
5 3
   
6 4
   my $op = param('op') || '';
7
-  my $state = 'start';
8 5
   
9 6
   my $errors;
10 7
   if ($op eq 'create') {
11
-    $state = 'create';
12 8
     
13
-    my $params = {
14
-      id => scalar param('id'),
15
-      password => scalar param('password'),
16
-      password2 => scalar param('password2')
17
-    };
18
-    my $id = param('id');
9
+    my $params = $api->params;
19 10
     my $validator = $self->app->validator;
20 11
     my $rule = [
21 12
       id => [
... ...
@@ -37,11 +28,17 @@
37 28
     my $vresult = $validator->validate($params, $rule);
38 29
     
39 30
     if ($vresult->is_ok) {
40
-      my $valid_params = $vresult->data;
41
-      my $id = delete $valid_params->{id};
42
-      $valid_params->{password} = md5_sum $valid_params->{password};
43
-      my $config_json = $api->json($valid_params);
44 31
       
32
+      # Valid parameters
33
+      my $params = $vresult->data;
34
+      my $id = delete $params->{id};
35
+      my ($password_encrypted, $salt)
36
+        = $api->encrypt_password($params->{password});
37
+      $params->{password} = $password_encrypted;
38
+      $params->{salt} = $salt;
39
+      my $config_json = $api->json($params);
40
+      
41
+      # Create user
45 42
       eval { app->manager->create_user($id, {config => $config_json}) };
46 43
       if ($@) {
47 44
         app->log->error($@);
... ...
@@ -53,10 +50,7 @@
53 50
         $self->redirect_to('current');
54 51
       }
55 52
     }
56
-    else {
57
-      $state = 'error';
58
-      $errors = $vresult->messages;
59
-    }
53
+    else { $errors = $vresult->messages }
60 54
   }
61 55
 %>
62 56
 
+67 -51
templates/auto/_login.html.ep
... ...
@@ -1,32 +1,31 @@
1 1
 <%
2 2
   use Mojo::JSON ();
3 3
   use Encode ();
4
-  use Gitprep::API;
5 4
   use Mojo::Util 'md5_sum';
6
-
7
-  my $api = Gitprep::API->new($self);
8 5
   
6
+  # API
7
+  my $api = gitprep_api;
8
+  
9
+  # Operator
9 10
   my $op = param('op') || '';
10
-  my $state = 'start';
11 11
   
12 12
   # DBI
13 13
   my $dbi = $self->app->dbi;
14 14
   
15
+  # Errors
16
+  my $error;
17
+  
18
+  # Login
15 19
   if ($op eq 'login') {
16
-    sleep 2;
17
-    $state = 'login';
20
+    sleep 3;
18 21
     
19
-    my $params = {
20
-      id => scalar param('id'),
21
-      password => scalar param('password'),
22
-    };
23
-    my $id = param('id');
22
+    # Validation
23
+    my $params = $api->params;
24 24
     my $validator = $self->app->validator;
25 25
     my $password_check = sub {
26 26
       my $values = shift;
27 27
       
28 28
       my ($id, $password) = @$values;
29
-      my $password_md5 = md5_sum $password;
30 29
       
31 30
       my $config_json
32 31
         = $dbi->model('user')->select('config', id => $id)->value;
... ...
@@ -35,9 +34,14 @@
35 34
       
36 35
       my $config = $api->json($config_json);
37 36
       
38
-      return $config->{password} eq $password_md5;
37
+      my $is_valid = $api->check_password(
38
+        $password,
39
+        $config->{salt},
40
+        $config->{password}
41
+      );
42
+      
43
+      return $is_valid;
39 44
     };
40
-    
41 45
     my $rule = [
42 46
       id => [
43 47
         'any'
... ...
@@ -51,36 +55,33 @@
51 55
           $password_check
52 56
         ]
53 57
     ];
54
-    my $vresult = $validator->validate($params, $rule);
58
+    my $vresult = $self->app->validator->validate($params, $rule);
55 59
     
56 60
     if ($vresult->is_ok) {
57
-      my $valid_params = $vresult->data;
58
-      my $id = $valid_params->{id};
59
-      my $password = $valid_params->{password};
60
-      my $password_md5 = md5_sum $password;
61 61
       
62
+      # Login success
63
+      my $params = $vresult->data;
64
+      my $id = $params->{id};
65
+      my $password = $params->{password};
62 66
       my $config_json = $self->app->dbi->model('user')->select('config', id => $id)->value;
63 67
       my $config = $api->json($config_json);
68
+      my $password_encrypted = $config->{password};
64 69
       my $admin = $config->{admin};
65 70
       session(user_id => $id);
66
-      session(user_password => $password_md5);
71
+      session(user_password => $password_encrypted);
67 72
       
73
+      # Go to admin page
68 74
       if ($admin) {
69 75
         $self->redirect_to('/_admin');
70 76
         return 1;
71 77
       }
78
+      # Go to user page
72 79
       else {
73 80
         $self->redirect_to("/$id");
74 81
         return 1;
75 82
       }
76 83
     }
77
-    else {
78
-      $state = 'error';
79
-    }
80
-  } elsif ($op eq 'logout') {
81
-    session(expires => 1);
82
-    $self->redirect_to('/');
83
-    return 1;
84
+    else { $error = 'User name or password is wrong' }
84 85
   }
85 86
 %>
86 87
 
... ...
@@ -88,31 +89,46 @@
88 89
 
89 90
   %= include '/include/header';
90 91
 
91
-  % my $id = '';
92
-  % if (flash('success')) {
93
-    <br>
94
-    <b><center>Start up success! Please login as admin user.</center></b>
95
-    % $id = flash('id');
96
-  % }
97
-  
98
-  <div class="border-gray" style="padding-top:15px;padding-left:60px;width:300px;margin-left:auto;margin-right:auto">
99
-    <form action="<%= url_for->query(op => 'login') %>" method="post">
100
-      <div class="control-group">
101
-        <label class="control-label" for="user-name">User name</label>
102
-        <div class="controls">
103
-          <%= input_tag id => $id, type => 'text', placeholder => 'User', id =>'user-name'%>
104
-        </div>
92
+  <div class="container">
93
+    % my $id = '';
94
+    % if (flash('admin_user_created')) {
95
+      % $id = 'admin';
96
+      <div class="alert alert-success">
97
+        <button type="button" class="close" data-dismiss="alert">&times;</button>
98
+        Admin user is created. Let's login as admin user.
105 99
       </div>
106
-      <div class="control-group">
107
-        <label class="control-label" for="input-password">Password</label>
108
-        <div class="controls">
109
-          <%= password_field 'password', id => 'input-password', placeholder => 'Password' %>
110
-        </div>
100
+    % }
101
+
102
+    % if ($error) {
103
+      <div class="alert alert-error">
104
+        <button type="button" class="close" data-dismiss="alert">&times;</button>
105
+        <div><%= $error %></div>
111 106
       </div>
112
-      <div class="control-group">
113
-        <div class="controls">
114
-          <button type="submit" class="btn">Sign in</button>
107
+    % }
108
+      
109
+    <div class="well border-gray"
110
+      style="background-color:white;padding-top:15px;padding-left:60px;width:300px;margin-left:auto;margin-right:auto"
111
+    >
112
+      <form action="<%= url_for->query(op => 'login') %>" method="post">
113
+        <div class="control-group">
114
+          <label class="control-label" for="user-name">User name</label>
115
+          <div class="controls">
116
+            <%= input_tag id => $id, type => 'text', placeholder => 'User', id =>'user-name'%>
117
+          </div>
115 118
         </div>
116
-      </div>
117
-    </form>
119
+        <div class="control-group">
120
+          <label class="control-label" for="input-password">Password</label>
121
+          <div class="controls">
122
+            <%= password_field 'password', id => 'input-password', placeholder => 'Password' %>
123
+          </div>
124
+        </div>
125
+        <div class="control-group">
126
+          <div class="controls">
127
+            <button type="submit" class="btn">Sign in</button>
128
+          </div>
129
+        </div>
130
+      </form>
131
+    </div>
118 132
   </div>
133
+
134
+  %= include '/include/footer';
+6
templates/auto/_logout.html.ep
... ...
@@ -0,0 +1,6 @@
1
+<%
2
+  my $from = param('from') || '/';
3
+  session(expires => 1);
4
+  $self->redirect_to($from);
5
+  return 1;
6
+%>
+76 -105
templates/auto/_start.html.ep
... ...
@@ -1,129 +1,100 @@
1 1
 <%
2
-  use Gitprep::API;
3 2
   use Mojo::Util 'md5_sum';
4 3
   
5
-  my $api = Gitprep::API->new($self);
4
+  # API
5
+  my $api = gitprep_api;
6 6
   
7
+  # Operator
7 8
   my $op = param('op') || '';
8
-  my $state = 'start';
9 9
   
10 10
   my $errors;
11 11
   if ($op eq 'create') {
12
-    $state = 'create';
13 12
     
14
-    my $params = {
15
-      id => scalar param('id'),
16
-      password => scalar param('password'),
17
-      password2 => scalar param('password2')
18
-    };
19
-    my $id = param('id');
20
-    my $validator = $self->app->validator;
21
-    my $rule = [
22
-      id => [
23
-        ['not_blank' => 'Input admin user.'],
24
-        [{'regex' => qr/^[a-zA-Z0-9_]+$/} => 'Admin User contain invalid character.'],
25
-        [{'length' => {max => 20}} => 'Admin User is too long.']
26
-      ],
27
-      password => [
28
-        ['not_blank' => 'Input password.'],
29
-        ['ascii' => 'Password contain invalid character.'],
30
-        [{'length' => {max => 20}} => 'Password is too long.']
31
-      ],
32
-      {password_check => [qw/password password2/]}
33
-        => {copy => 0}
34
-        => [
35
-          ['duplication' => "Two password don't match"]
36
-        ]
37
-    ];
38
-    my $vresult = $validator->validate($params, $rule);
13
+    # Sleep to protect password atack
14
+    sleep 3;
39 15
     
40
-    if ($vresult->is_ok) {
41
-      my $valid_params = $vresult->data;
42
-      my $id = delete $valid_params->{id};
43
-      $valid_params->{admin} = 1;
44
-      $valid_params->{password} = md5_sum $valid_params->{password};
45
-      my $config_json = $api->json($valid_params);
46
-      
47
-      $self->app->dbi->model('user')->insert({id => $id, config => $config_json});
48
-      
49
-      $self->flash(success => 1);
50
-      $self->flash(id => $id);
51
-      $self->redirect_to('/_login');
16
+    # Check existence admin user
17
+    my $admin_user = app->dbi->model('user')->select(id => 'admin')->one;
18
+    if ($admin_user) {
19
+      $errors = ['admin user already exists'];
52 20
     }
53 21
     else {
54
-      $state = 'error';
55
-      $errors = $vresult->messages;
22
+      # Validation
23
+      my $params = $api->params;
24
+      my $rule = [
25
+        password => [
26
+          ['not_blank' => 'Password is emplty'],
27
+          ['ascii' => 'Password contain invalid character.'],
28
+          [{'length' => {max => 20}} => 'Password is too long.']
29
+        ],
30
+        {password_check => [qw/password password2/]}
31
+          => {copy => 0}
32
+          => [
33
+            ['duplication' => "Two password don't match"]
34
+          ]
35
+      ];
36
+      my $vresult = $self->app->validator->validate($params, $rule);
37
+      
38
+      if ($vresult->is_ok) {
39
+        
40
+        # Valida parameters
41
+        my $params = $vresult->data;
42
+        my $id = 'admin';
43
+        $params->{admin} = 1;
44
+        my ($password_encryped, $salt)
45
+          = $api->encrypt_password($params->{password});
46
+        $params->{password} = $password_encryped;
47
+        $params->{salt} = $salt;
48
+        my $config_json = $api->json($params);
49
+        
50
+        # Create admin user
51
+        $self->app->dbi->model('user')->insert({id => $id, config => $config_json});
52
+        
53
+        # Redirect
54
+        $self->flash(admin_user_created => 1);
55
+        $self->redirect_to('/_login');
56
+      }
57
+      else { $errors = $vresult->messages }
56 58
     }
57 59
   }
58 60
 %>
59 61
 
60
-%= stylesheet begin
61
-  .error {
62
-    margin-left:auto;
63
-    margin-right:auto;
64
-    width:300px;
65
-    color:red;
66
-    margin-bottom:10px;
67
-  }
68
-  .account_panel {
69
-    margin-top:50px;
70
-    margin-left:auto;
71
-    margin-right:auto;
72
-    width:500px;
73
-    background-color:#e6f1f6;
74
-    padding:30px;
75
-    border:1px solid #c5d5dd;
76
-    border-radius:7px;
77
-  }
78
-  .account {
79
-    margin-left:auto;
80
-    margin-right:auto;
81
-    width:300px;
82
-    margin-bottom:10px;
83
-  }
84
-  .account_panel .atitle {
85
-    font-size:130%;
86
-    maring-top:30px;
87
-  }
88
-  .submit {
89
-    text-align:center;
90
-  }
91
-  .submit input {
92
-    width:200px;
93
-    height:40px;
94
-  }
95
-% end
96
-
97 62
 % layout 'common';
98
-%= include '/css/common';
99 63
 
100
-% if ($state eq 'start' || $state eq 'error') {
101
-  <div class="account_panel">
102
-    % if ($state eq 'error') {
103
-      <div class="error">
64
+  %= include '/include/header';
65
+
66
+  <div class="container">
67
+    % if ($errors) {
68
+      <div class="alert alert-error">
69
+        <button type="button" class="close" data-dismiss="alert">&times;</button>
104 70
         % for my $error (@$errors) {
105 71
           <div><%= $error %></div>
106 72
         % }
107 73
       </div>
108 74
     % }
109
-    <form action="<%= url_for->query(op => 'create') %>" method="post">
110
-      <div class="account">
111
-        <table>
112
-          <tr class="auser">
113
-            <td>Admin User: </td>
114
-            <td><%= input_tag id => 'admin' %></td>
115
-          </tr>
116
-          <tr class="apassword">
117
-            <td>Password: </td>
118
-            <td><%= password_field 'password' %></td>
119
-          </tr>
120
-          <tr class="apassword">
121
-            <td>(Again): </td>
122
-            <td><%= password_field 'password2' %></td>
123
-          </tr>
124
-        </table>
125
-      </div>
126
-      <div class="submit"><input type="submit" value="Start Git Prep!"></div>
127
-    </form>
75
+    <div class="text-center"><h3>Create Admin User</h3></div>
76
+    <div class="well" style="background-color:white;padding-top:15px;padding-left:60px;width:300px;margin-left:auto;margin-right:auto">
77
+      <form action="<%= url_for->query(op => 'create') %>" method="post">
78
+        <div class="control-group">
79
+          <label class="control-label" for="user-name">User name</label>
80
+          <div>
81
+            <b>admin</b>
82
+          </div>
83
+        </div>
84
+        <div class="control-group">
85
+          <label class="control-label" for="input-password">Password</label>
86
+          <div class="controls">
87
+            <%= password_field 'password', id => 'input-password', placeholder => 'Password' %>
88
+            <%= password_field 'password2', id => 'input-password', placeholder => 'Password Again' %>
89
+          </div>
90
+        </div>
91
+        <div class="control-group">
92
+          <div class="controls">
93
+            <button type="submit" class="btn">Create Admin User</button>
94
+          </div>
95
+        </div>
96
+      </form>
97
+    </div>
128 98
   </div>
129
-% }
99
+
100
+  %= include '/include/footer';
+6 -3
templates/include/header.html.ep
... ...
@@ -11,11 +11,14 @@
11 11
       % if ($api->logined) {
12 12
         <div  style="margin-top:5px">
13 13
           <div>
14
-            % my $user = session('user_id');
14
+            % my $user = session('user_id') || '';
15 15
             <i class="icon-user"></i><a href="<%= url_for("/$user") %>"><%= $user %></a>
16 16
           </div>
17
-          <a class="btn btn-small" href="<%= url_for("/_admin/create") %>">Create a new repo</a>
18
-          <a class="btn btn-small" href="<%= url_for("/_login?op=logout") %>">Sign out</a>
17
+          % unless ($user eq 'admin') {
18
+            <a class="btn btn-small" href="<%= url_for("/_admin/create") %>">Create a new repo</a>
19
+          % }
20
+          % my $url = url_with->to_abs;
21
+          <a class="btn btn-small" href="<%= url_for("/_logout?from=$url") %>">Sign out</a>
19 22
         </div>
20 23
       % } else {
21 24
         <div  style="margin-top:15px">
+114
templates/reset-password.html.ep
... ...
@@ -0,0 +1,114 @@
1
+<%
2
+  use Mojo::Util 'md5_sum';
3
+  
4
+  # API
5
+  my $api = gitprep_api;
6
+  
7
+  # Operator
8
+  my $op = param('op') || '';
9
+  
10
+  # Error
11
+  my $errors;
12
+  
13
+  # Reset password
14
+  if ($op eq 'reset') {
15
+    
16
+    # Sleep to protect password atack
17
+    sleep 3;
18
+    
19
+    # Check existence admin user
20
+    my $admin_user = app->dbi->model('user')->select(id => 'admin')->one;
21
+    
22
+    # Reset password
23
+    if ($admin_user) {
24
+      # Validation
25
+      my $params = $api->params;
26
+      my $rule = [
27
+        password => [
28
+          ['not_blank' => 'Password is emplty'],
29
+          ['ascii' => 'Password contain invalid character.'],
30
+          [{'length' => {max => 20}} => 'Password is too long.']
31
+        ],
32
+        {password_check => [qw/password password2/]}
33
+          => {copy => 0}
34
+          => [
35
+            ['duplication' => "Two password don't match"]
36
+          ]
37
+      ];
38
+      my $vresult = $self->app->validator->validate($params, $rule);
39
+      
40
+      if ($vresult->is_ok) {
41
+        
42
+        # Valid parameters
43
+        my $valid_params = $vresult->data;
44
+        my $id = 'admin';
45
+        my ($new_password, $salt)
46
+          = $api->sulted_md5_sum($valid_params->{password});
47
+        
48
+        # Create admin user
49
+        my $dbi = app->dbi;
50
+        
51
+        my $config_json = $dbi->model('user')->select(id => $id)->one;
52
+        if ($config) {
53
+          my $config = $api->json($config_json);
54
+          $config->{password} = $new_password;
55
+          $config->{salt} = $salt;
56
+          $self->app->dbi->model('user')->update({config => $config_json}, id => $id);
57
+        }
58
+        else { $errors = ['Internal Error'] }
59
+        
60
+        # Redirect
61
+        flash(message => 'Password is reset');
62
+        $self->redirect_to('current');
63
+      }
64
+      else { $errors = $vresult->messages }
65
+    }
66
+    else { $errors = ['admin user no exists'] }
67
+  }
68
+%>
69
+
70
+% layout 'common';
71
+
72
+  %= include '/include/header';
73
+
74
+  <div class="container">
75
+    % if (flash($message)) {
76
+      <div class="alert alert-success">
77
+        <button type="button" class="close" data-dismiss="alert">&times;</button>
78
+        <div><%= $message %></div>
79
+    % }
80
+    
81
+    % if ($errors) {
82
+      <div class="alert alert-error">
83
+        <button type="button" class="close" data-dismiss="alert">&times;</button>
84
+        % for my $error (@$errors) {
85
+          <div><%= $error %></div>
86
+        % }
87
+      </div>
88
+    % }
89
+    <div class="text-center"><h3>Reset Admin Password</h3></div>
90
+    <div class="well" style="background-color:white;padding-top:15px;padding-left:60px;width:300px;margin-left:auto;margin-right:auto">
91
+      <form action="<%= url_for->query(op => 'reset') %>" method="post">
92
+        <div class="control-group">
93
+          <label class="control-label" for="user-name">User name</label>
94
+          <div>
95
+            <b>admin</b>
96
+          </div>
97
+        </div>
98
+        <div class="control-group">
99
+          <label class="control-label" for="input-password">Password</label>
100
+          <div class="controls">
101
+            <%= password_field 'password', id => 'input-password', placeholder => 'Password' %>
102
+            <%= password_field 'password2', id => 'input-password', placeholder => 'Password Again' %>
103
+          </div>
104
+        </div>
105
+        <div class="control-group">
106
+          <div class="controls">
107
+            <button type="submit" class="btn">Reset Admin Password</button>
108
+          </div>
109
+        </div>
110
+      </form>
111
+    </div>
112
+  </div>
113
+
114
+  %= include '/include/footer';