lines-engine 0.5 → 0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +100 -70
- data/Guardfile +33 -0
- data/README.rdoc +12 -8
- data/app/assets/stylesheets/lines/article.scss +55 -31
- data/app/assets/stylesheets/lines/fonts.scss +1 -34
- data/app/assets/stylesheets/lines/general.scss +2 -0
- data/app/assets/stylesheets/lines/media_queries.scss +11 -5
- data/app/assets/stylesheets/lines/variables_and_mixins.scss +6 -0
- data/app/controllers/lines/articles_controller.rb +3 -2
- data/app/controllers/lines/password_resets_controller.rb +84 -0
- data/app/helpers/lines/application_helper.rb +2 -2
- data/app/mailers/lines/user_mailer.rb +11 -0
- data/app/models/lines/user.rb +41 -5
- data/app/views/layouts/lines/application.html.erb +1 -0
- data/app/views/lines/password_resets/edit.html.erb +26 -0
- data/app/views/lines/password_resets/new.html.erb +8 -0
- data/app/views/lines/sessions/new.html.erb +4 -0
- data/app/views/lines/shared/_flash.html.erb +8 -0
- data/app/views/lines/user_mailer/password_reset.html.erb +10 -0
- data/app/views/lines/user_mailer/password_reset.text.erb +8 -0
- data/certs/lines-engine.pem +21 -0
- data/config/lines_config.yml +4 -1
- data/config/routes.rb +5 -0
- data/db/migrate/20150421093311_add_reset_password_fields_to_users.rb +6 -0
- data/lib/generators/lines/copy_styles_generator.rb +13 -0
- data/lib/lines/version.rb +1 -1
- data/lines.gemspec +5 -2
- data/spec/dummy/config/environments/development.rb +5 -0
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/config/lines_config.yml +4 -1
- data/spec/dummy/db/schema.rb +3 -1
- data/spec/dummy/test/mailers/previews/user_mailer_preview.rb +11 -0
- data/spec/features/article_spec.rb +0 -1
- data/spec/features/password_reset_spec.rb +48 -0
- data/spec/mailers/lines/user_mailer_spec.rb +24 -0
- metadata +51 -12
- metadata.gz.sig +0 -0
@@ -1,37 +1,4 @@
|
|
1
1
|
@import url(//fonts.googleapis.com/css?family=Merriweather:400,400italic,700);
|
2
2
|
@import url(//fonts.googleapis.com/css?family=Merriweather+Sans:400,700);
|
3
3
|
@import url(//fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700,700italic,900);
|
4
|
-
@import url(//fonts.googleapis.com/css?family=
|
5
|
-
|
6
|
-
@font-face {
|
7
|
-
font-family: 'BPmono';
|
8
|
-
src: url('/assets/BPmono-webfont.eot');
|
9
|
-
src: url('/assets/BPmono-webfont.eot?#iefix') format('embedded-opentype'),
|
10
|
-
url('/assets/BPmono-webfont.woff') format('woff'),
|
11
|
-
url('/assets/BPmono-webfont.ttf') format('truetype'),
|
12
|
-
url('/assets/BPmono-webfont.svg#BPmono-webfont') format('svg');
|
13
|
-
font-weight: normal;
|
14
|
-
font-style: normal;
|
15
|
-
}
|
16
|
-
|
17
|
-
@font-face {
|
18
|
-
font-family: 'BPmono';
|
19
|
-
src: url('/assets/BPmonoItalics-webfont.eot');
|
20
|
-
src: url('/assets/BPmonoItalics-webfont.eot?#iefix') format('embedded-opentype'),
|
21
|
-
url('/assets/BPmonoItalics-webfont.woff') format('woff'),
|
22
|
-
url('/assets/BPmonoItalics-webfont.ttf') format('truetype'),
|
23
|
-
url('/assets/BPmonoItalics-webfont.svg#BPmonoItalics-webfont') format('svg');
|
24
|
-
font-weight: normal;
|
25
|
-
font-style: italic;
|
26
|
-
}
|
27
|
-
|
28
|
-
@font-face {
|
29
|
-
font-family: 'BPmono';
|
30
|
-
src: url('/assets/BPmonoBold-webfont.eot');
|
31
|
-
src: url('/assets/BPmonoBold-webfont.eot?#iefix') format('embedded-opentype'),
|
32
|
-
url('/assets/BPmonoBold-webfont.woff') format('woff'),
|
33
|
-
url('/assets/BPmonoBold-webfont.ttf') format('truetype'),
|
34
|
-
url('/assets/BPmonoBold-webfont.svg#BPmonoBold-webfont') format('svg');
|
35
|
-
font-weight: bold;
|
36
|
-
font-style: normal;
|
37
|
-
}
|
4
|
+
@import url(//fonts.googleapis.com/css?family=Fira+Mono:400,700&subset=latin,latin-ext);
|
@@ -8,11 +8,11 @@ smaller than 975px
|
|
8
8
|
smaller than 800px
|
9
9
|
*************************************************************************************/
|
10
10
|
@media screen and (max-width: 800px) {
|
11
|
-
html { font-size: 16px; }
|
11
|
+
html, body { font-size: 16px; }
|
12
12
|
.article {
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
.article_header, .article_teaser, .article_content {
|
14
|
+
& > * { padding: 0 8%; }
|
15
|
+
}
|
16
16
|
}
|
17
17
|
}
|
18
18
|
|
@@ -20,7 +20,7 @@ smaller than 800px
|
|
20
20
|
smaller than 600px
|
21
21
|
*************************************************************************************/
|
22
22
|
@media screen and (max-width: 600px) {
|
23
|
-
html { font-size: 14px; }
|
23
|
+
html, body { font-size: 14px; }
|
24
24
|
.article.small { width: 100%; max-width: 100%; }
|
25
25
|
}
|
26
26
|
|
@@ -28,4 +28,10 @@ smaller than 600px
|
|
28
28
|
smaller than 480px
|
29
29
|
*************************************************************************************/
|
30
30
|
@media screen and (max-width: 480px) {
|
31
|
+
html, body { font-size: 14px; }
|
32
|
+
.article {
|
33
|
+
.article_header h1 {
|
34
|
+
font-size: 2.5em;
|
35
|
+
}
|
36
|
+
}
|
31
37
|
}
|
@@ -4,9 +4,15 @@ $darkgrey: #333;
|
|
4
4
|
$lightgrey: #666;
|
5
5
|
$orange: #ee4422;
|
6
6
|
|
7
|
+
$red: #c10020;
|
8
|
+
$black: #100719;
|
9
|
+
$grey: #9793a0;
|
10
|
+
$white: #fff;
|
11
|
+
|
7
12
|
$SourceSansPro: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif;
|
8
13
|
$MerriweatherSans: 'Merriweather Sans', 'Helvetica', 'Arial', sans-serif;
|
9
14
|
$Merriweather: 'Merriweather', 'Georgia', serif;
|
15
|
+
$Monospace: 'Fira mono', monospace;
|
10
16
|
|
11
17
|
@mixin addTransition($x) {
|
12
18
|
transition: all $x+s ease;
|
@@ -24,8 +24,9 @@ module Lines
|
|
24
24
|
end
|
25
25
|
|
26
26
|
if @articles.first_page?
|
27
|
-
@first_article = Article.published.first
|
28
|
-
|
27
|
+
if @first_article = Article.published.first
|
28
|
+
@first_article.teaser = nil unless @first_article.teaser.present?
|
29
|
+
end
|
29
30
|
end
|
30
31
|
|
31
32
|
set_meta_tags title: SITE_TITLE,
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require_dependency "lines/application_controller"
|
2
|
+
|
3
|
+
module Lines
|
4
|
+
class PasswordResetsController < ApplicationController
|
5
|
+
|
6
|
+
layout 'lines/admin'
|
7
|
+
|
8
|
+
before_action :get_user, only: [:edit, :update]
|
9
|
+
before_action :valid_user, only: [:edit, :update]
|
10
|
+
|
11
|
+
def new
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
@user = User.find_by(email: params[:password_reset][:email].downcase)
|
16
|
+
if @user
|
17
|
+
@user.create_reset_digest
|
18
|
+
@user.send_password_reset_email
|
19
|
+
flash[:info] = "Email sent with password reset instructions"
|
20
|
+
redirect_to root_url
|
21
|
+
else
|
22
|
+
flash.now[:error] = "Email address not found"
|
23
|
+
render 'new'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def edit
|
28
|
+
end
|
29
|
+
|
30
|
+
def update
|
31
|
+
if password_blank?
|
32
|
+
flash.now[:error] = "Password can't be blank"
|
33
|
+
render 'edit'
|
34
|
+
elsif wrong_password_confirmation?
|
35
|
+
flash.now[:error] = "Password confirmation does not match"
|
36
|
+
render 'edit'
|
37
|
+
elsif @user.update_attributes(user_params)
|
38
|
+
# deletr reset_digest and reset_sent_at
|
39
|
+
@user.update_attributes(reset_digest: nil, reset_sent_at: nil)
|
40
|
+
flash[:success] = "Password has been reset. You can now log in with your new password."
|
41
|
+
redirect_to new_session_path
|
42
|
+
else
|
43
|
+
render 'edit'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Use strong_params
|
51
|
+
def user_params
|
52
|
+
params.require(:user).permit(:email, :password, :password_confirmation)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns true if password is blank.
|
56
|
+
def password_blank?
|
57
|
+
params[:user][:password].blank?
|
58
|
+
end
|
59
|
+
|
60
|
+
def wrong_password_confirmation?
|
61
|
+
params[:user][:password] != params[:user][:password_confirmation]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks expiration of reset token.
|
65
|
+
def check_expiration
|
66
|
+
if @user.password_reset_expired?
|
67
|
+
flash[:error] = "Password reset has expired."
|
68
|
+
redirect_to new_password_reset_url
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_user
|
73
|
+
@user = User.find_by(email: params[:email])
|
74
|
+
end
|
75
|
+
|
76
|
+
# Confirms a valid user.
|
77
|
+
def valid_user
|
78
|
+
unless (@user && @user.authenticated?(:reset, params[:id]))
|
79
|
+
redirect_to root_url
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -8,9 +8,9 @@ module Lines
|
|
8
8
|
# Renders the teaser for an article.
|
9
9
|
def render_teaser(article, article_counter=0)
|
10
10
|
if article_counter < 0
|
11
|
-
teaser = article.teaser.present? ? markdown(article.teaser) : nil
|
11
|
+
teaser = article.teaser && article.teaser.present? ? markdown(article.teaser) : nil
|
12
12
|
else
|
13
|
-
teaser = article.teaser.present? ? format_code(article.teaser) : format_code(article.content)
|
13
|
+
teaser = article.teaser && article.teaser.present? ? format_code(article.teaser) : format_code(article.content)
|
14
14
|
end
|
15
15
|
teaser
|
16
16
|
end
|
data/app/models/lines/user.rb
CHANGED
@@ -15,16 +15,53 @@
|
|
15
15
|
module Lines
|
16
16
|
|
17
17
|
class User < ActiveRecord::Base
|
18
|
-
#
|
18
|
+
# Use bcrypt-ruby to encrypt passwords
|
19
19
|
has_secure_password validations: false
|
20
|
-
|
21
|
-
|
20
|
+
|
21
|
+
attr_accessor :reset_token
|
22
22
|
|
23
23
|
# Validations
|
24
24
|
validates :password, length: { minimum: 6 }, if: :validate_password?
|
25
|
+
validates :password, presence: true, on: :create
|
25
26
|
validates :email, uniqueness: true, presence: true
|
26
27
|
|
27
|
-
|
28
|
+
# Sets +rest_digest+ and +reset_sent_at+ for password reset.
|
29
|
+
def create_reset_digest
|
30
|
+
self.reset_token = User.generate_token
|
31
|
+
update_attribute(:reset_digest, User.digest(reset_token))
|
32
|
+
update_attribute(:reset_sent_at, Time.zone.now)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sends email with instructions how to reset password.
|
36
|
+
def send_password_reset_email
|
37
|
+
UserMailer.password_reset(self).deliver
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns true if the given token matches the digest.
|
41
|
+
def authenticated?(attribute, token)
|
42
|
+
digest = send("#{attribute}_digest")
|
43
|
+
return false if digest.nil?
|
44
|
+
BCrypt::Password.new(digest).is_password?(token)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Generate a random token.
|
49
|
+
def User.generate_token
|
50
|
+
SecureRandom.urlsafe_base64
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the hash digest of the given string.
|
54
|
+
def User.digest(string)
|
55
|
+
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
|
56
|
+
BCrypt::Password.create(string, cost: cost)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns true if a password reset has expired.
|
60
|
+
def password_reset_expired?
|
61
|
+
reset_sent_at < 2.hours.ago
|
62
|
+
end
|
63
|
+
|
64
|
+
|
28
65
|
private
|
29
66
|
# Returns +true+ if a password is submitted
|
30
67
|
def validate_password?
|
@@ -32,5 +69,4 @@ module Lines
|
|
32
69
|
end
|
33
70
|
|
34
71
|
end
|
35
|
-
|
36
72
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<h1>Reset password</h1>
|
2
|
+
|
3
|
+
<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
|
4
|
+
<% if @user.errors.any? %>
|
5
|
+
<div id="error_explanation">
|
6
|
+
<div class="alert errors">
|
7
|
+
The form contains <%= pluralize(@user.errors.count, "error") %>.
|
8
|
+
</div>
|
9
|
+
<ul>
|
10
|
+
<% @user.errors.full_messages.each do |msg| %>
|
11
|
+
<li><%= msg %></li>
|
12
|
+
<% end %>
|
13
|
+
</ul>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<%= hidden_field_tag :email, @user.email %>
|
18
|
+
|
19
|
+
<%= f.label :password %>
|
20
|
+
<%= f.password_field :password %>
|
21
|
+
|
22
|
+
<%= f.label :password_confirmation, "Confirmation" %>
|
23
|
+
<%= f.password_field :password_confirmation %>
|
24
|
+
|
25
|
+
<%= f.submit "Update password" %>
|
26
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<% flash.each do |name, msg| %>
|
2
|
+
<% if msg.is_a?(String) %>
|
3
|
+
<div class="alert alert-<%= name == :notice ? "success" : "error" %>">
|
4
|
+
<a class="close" data-dismiss="alert">×</a>
|
5
|
+
<%= content_tag :div, msg, id: "flash_#{name}" %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<h1>Password reset</h1>
|
2
|
+
|
3
|
+
<p>To reset your password click the link below:</p>
|
4
|
+
|
5
|
+
<p><%= edit_password_reset_url(@user.reset_token, email: @user.email) %></p>
|
6
|
+
|
7
|
+
<p>This link will expire in two hours.</p>
|
8
|
+
|
9
|
+
<p>If you did not request your password to be reset, please ignore this email and
|
10
|
+
your password will stay as it is.</p>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
To reset your password click the link below:
|
2
|
+
|
3
|
+
<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>
|
4
|
+
|
5
|
+
This link will expire in two hours.
|
6
|
+
|
7
|
+
If you did not request your password to be reset, please ignore this email and
|
8
|
+
your password will stay as it is.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQ0wCwYDVQQDDARpbmZv
|
3
|
+
MRYwFAYKCZImiZPyLGQBGRYGb3BvbG9vMRIwEAYKCZImiZPyLGQBGRYCZGUwHhcN
|
4
|
+
MTUwNDIxMTIyMzQ5WhcNMTYwNDIwMTIyMzQ5WjA7MQ0wCwYDVQQDDARpbmZvMRYw
|
5
|
+
FAYKCZImiZPyLGQBGRYGb3BvbG9vMRIwEAYKCZImiZPyLGQBGRYCZGUwggEiMA0G
|
6
|
+
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxEm+2kvkvFdAyi6VKiSPvEbFLgvC7
|
7
|
+
+MnhXzkFGLOb4UFNB0uSK2xGdBf2QVDWmK8W19UQNWoXHv5Pfni0wmUJ5yoP7lNg
|
8
|
+
qMqZFK0tkDImsQRrfOndqNylVyy9vBn/YtyAYG/v+IGxEW59OoL1uvX4FnxJ3C07
|
9
|
+
P8I0gWPUlw4/UFSpzTj1r4IEeN21oYlgCSz98O5vZVkRZAVNmotxcn/+vcpERjZ+
|
10
|
+
ugolMzyEqC5Uztos/D4pSrtum/UIhDbYfzkoI78hZjsaKQQwbmJd9/WzI5FUqEhS
|
11
|
+
ucANoSTcibahVQ5Qg1AedJCTgO5Z2NBdCSXce8q5wx5W7BqNPNjML5VpAgMBAAGj
|
12
|
+
bzBtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSB4W9llLnHPI5e
|
13
|
+
OUL+IbfDGahfYjAZBgNVHREEEjAQgQ5pbmZvQG9wb2xvby5kZTAZBgNVHRIEEjAQ
|
14
|
+
gQ5pbmZvQG9wb2xvby5kZTANBgkqhkiG9w0BAQUFAAOCAQEAxSYCQ1c3Am0fpQZW
|
15
|
+
NNOiHDAcn8k8B4rFp0/llzsIH0WOttTnhYYHqKdnpWJms2xPOPppb9MD8d+HpBn4
|
16
|
+
mCIzpjfzE0RQ0RzzLUdfDXLJPjOaJ/vtKZMMBxfMivLfx8HZTGrVVVE3l8wPav/2
|
17
|
+
CMUyx9n4Yn6xztVzRbadZp3Q1O/Bsg7dFz7xVkxUEaM1oHGFR+HrWRX2ZyDpaUHJ
|
18
|
+
fNEZV3vwM6Ye3VRzeM3R7Z1R9e9FbUK6awQt/x4gAaI9m3LS85lbwuibKKpdYupp
|
19
|
+
5BNWA8E1pTLFIGuj2vWGF3LmLHATeyBGXNDBRf6XZUG124zSK5pxkJ5zm2uBhu00
|
20
|
+
+pn0Gw==
|
21
|
+
-----END CERTIFICATE-----
|
data/config/lines_config.yml
CHANGED
@@ -49,7 +49,10 @@ secret_token: "afa4a436180a2febe9b814c6a57b6f3ad298aa82ce0ab4c9e8238713764b665e9
|
|
49
49
|
# Environment dependent configuration. Place your URLs here
|
50
50
|
development:
|
51
51
|
host: "localhost:3000"
|
52
|
+
from_email: 'noreply@example.com'
|
52
53
|
test:
|
53
54
|
host: "test.local"
|
55
|
+
from_email: 'noreply@example.com'
|
54
56
|
production:
|
55
|
-
host: "blog.opoloo.com"
|
57
|
+
host: "blog.opoloo.com"
|
58
|
+
from_email: 'noreply@example.com'
|
data/config/routes.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
Lines::Engine.routes.draw do
|
2
2
|
|
3
|
+
get 'password_resets/new'
|
4
|
+
|
5
|
+
get 'password_resets/edit'
|
6
|
+
|
3
7
|
get 'login', to: 'sessions#new', as: 'login'
|
4
8
|
get 'logout', to: 'sessions#destroy', as: 'logout'
|
5
9
|
get 'tags/:tag', to: 'articles#index', as: :tag
|
6
10
|
|
7
11
|
resources :sessions
|
12
|
+
resources :password_resets, only: [:new, :create, :edit, :update]
|
8
13
|
|
9
14
|
resources :articles, only: [:index, :show] do
|
10
15
|
get 'page/:page', action: :index, on: :collection
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Lines
|
4
|
+
class CopyStylesGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path('..', __FILE__)
|
6
|
+
|
7
|
+
def copy_stylesheets
|
8
|
+
FileUtils.mkdir_p "app/assets/stylesheets/lines"
|
9
|
+
directory('../../../app/assets/stylesheets/lines/', 'app/assets/stylesheets/lines/', {:exclude_pattern => /admin/} )
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|