mirror of
https://github.com/zhigang1992/tsemple.git
synced 2026-01-12 22:53:00 +08:00
Add user confirmation module
This commit is contained in:
@@ -1,3 +1,16 @@
|
||||
#user-confirm-required {
|
||||
padding: $line-height-computed 0;
|
||||
background: white;
|
||||
|
||||
.alert {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
background: white;
|
||||
padding: 20px 0;
|
||||
|
||||
33
app/controllers/users/confirmations_controller.rb
Normal file
33
app/controllers/users/confirmations_controller.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
class Users::ConfirmationsController < ApplicationController
|
||||
before_action :login_required
|
||||
before_action :access_limiter, only: [:create]
|
||||
|
||||
def show
|
||||
if params[:token].present?
|
||||
@user = User.find_by_confirmation_token(params[:token])
|
||||
if @user && @user == current_user
|
||||
@user.confirm
|
||||
flash[:success] = I18n.t('users.confirmations.confirm_success')
|
||||
redirect_to settings_profile_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
UserMailer.confirmation(current_user.id).deliver
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def access_limiter
|
||||
key = "verifies:limiter:#{request.remote_ip}"
|
||||
if $redis.get(key).to_i > 0
|
||||
render :limiter
|
||||
else
|
||||
$redis.incr(key)
|
||||
if $redis.ttl(key) == -1
|
||||
$redis.expire(key, 60)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,4 +9,10 @@ class UserMailer < ActionMailer::Base
|
||||
mail(to: @user.email,
|
||||
subject: I18n.t('user_mailer.password_reset.subject'))
|
||||
end
|
||||
|
||||
def confirmation(user_id)
|
||||
@user = User.find(user_id)
|
||||
mail(to: @user.email,
|
||||
subject: I18n.t('user_mailer.confirmation.subject'))
|
||||
end
|
||||
end
|
||||
|
||||
22
app/models/concerns/user/confirmable.rb
Normal file
22
app/models/concerns/user/confirmable.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
class User
|
||||
module Confirmable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def confirm
|
||||
update_attribute :confirmed, true
|
||||
end
|
||||
|
||||
def confirmation_token
|
||||
self.class.verifier_for('confirmation').generate([id, Time.now])
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def find_by_confirmation_token(token)
|
||||
user_id, timestamp = verifier_for('confirmation').verify(token)
|
||||
User.find_by(id: user_id) if timestamp > 1.hour.ago
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,5 @@
|
||||
class User < ActiveRecord::Base
|
||||
include Confirmable
|
||||
include Gravtastic
|
||||
gravtastic secure: true, default: 'wavatar', rating: 'G', size: 48
|
||||
mount_uploader :avatar, AvatarUploader
|
||||
|
||||
@@ -72,4 +72,6 @@ html
|
||||
a href=topics_path
|
||||
= t '.community'
|
||||
|
||||
- if login? and !current_user.confirmed?
|
||||
= render 'share/user_confirm_required'
|
||||
= yield
|
||||
|
||||
19
app/views/share/_user_confirm_required.html.slim
Normal file
19
app/views/share/_user_confirm_required.html.slim
Normal file
@@ -0,0 +1,19 @@
|
||||
#user-confirm-required
|
||||
.container
|
||||
.alert.alert-warning
|
||||
p
|
||||
= t '.description', email: current_user.email
|
||||
p
|
||||
a.btn.btn-default href=users_confirmation_path data-remote="true" data-method="post"
|
||||
= t '.resend_email'
|
||||
'
|
||||
a.btn href=settings_account_path
|
||||
= t '.change_email'
|
||||
|
||||
#user-confirm-required-modal.modal.fade
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
button.close data-dismiss="modal"
|
||||
| ×
|
||||
.modal-title
|
||||
1
app/views/user_mailer/confirmation.html.slim
Normal file
1
app/views/user_mailer/confirmation.html.slim
Normal file
@@ -0,0 +1 @@
|
||||
= sanitize markdown t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host']
|
||||
1
app/views/user_mailer/confirmation.text.erb
Normal file
1
app/views/user_mailer/confirmation.text.erb
Normal file
@@ -0,0 +1 @@
|
||||
<%= t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host'] %>
|
||||
1
app/views/users/confirmations/create.js.erb
Normal file
1
app/views/users/confirmations/create.js.erb
Normal file
@@ -0,0 +1 @@
|
||||
$('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_success' %>').end().modal();
|
||||
1
app/views/users/confirmations/limiter.js.erb
Normal file
1
app/views/users/confirmations/limiter.js.erb
Normal file
@@ -0,0 +1 @@
|
||||
$('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_limiter' %>').end().modal();
|
||||
10
app/views/users/confirmations/show.html.slim
Normal file
10
app/views/users/confirmations/show.html.slim
Normal file
@@ -0,0 +1,10 @@
|
||||
.main
|
||||
.container
|
||||
.row
|
||||
.col-md-8.col-md-push-2
|
||||
.panel
|
||||
.panel-heading
|
||||
h3.panel-title
|
||||
= t '.confirm_fail'
|
||||
.panel-body
|
||||
= t '.confirmation_toke_invalid_or_expired_please_resend'
|
||||
@@ -6,6 +6,7 @@ search:
|
||||
- '*.rb'
|
||||
- '*.slim'
|
||||
- '*.text.erb'
|
||||
- '*.js.erb'
|
||||
|
||||
ignore_unused:
|
||||
- activerecord.*
|
||||
|
||||
@@ -6,9 +6,19 @@ en:
|
||||
description: Description
|
||||
name: Name
|
||||
slug: Slug
|
||||
topics_count: Topics count
|
||||
comment:
|
||||
commentable: Commentable
|
||||
created_at: Created at
|
||||
user: User
|
||||
topic:
|
||||
comments_count: Comment count
|
||||
created_at: Created at
|
||||
user: User
|
||||
user:
|
||||
avatar: Avatar
|
||||
bio: Bio
|
||||
created_at: Created at
|
||||
email: Email
|
||||
locale: Locale
|
||||
name: Name
|
||||
@@ -22,8 +32,7 @@ en:
|
||||
email:
|
||||
format: invalid email format
|
||||
username:
|
||||
format: may only contain alphanumeric characters or dashes and cannot
|
||||
begin with a dash
|
||||
format: may only contain alphanumeric characters or dashes and cannot begin with a dash
|
||||
admin:
|
||||
attachments:
|
||||
index:
|
||||
@@ -37,8 +46,7 @@ en:
|
||||
form:
|
||||
create_category: Create category
|
||||
permanently_delete: Permanently delete
|
||||
permanently_delete_confirm: Are you sure you want to permanently delete this
|
||||
category?
|
||||
permanently_delete_confirm: Are you sure you want to permanently delete this category?
|
||||
save_changes: Save changes
|
||||
index:
|
||||
categories: Categories
|
||||
@@ -92,8 +100,7 @@ en:
|
||||
show:
|
||||
lock: Lock
|
||||
permanently_delete: Permanently delete
|
||||
permanently_delete_confirm: Are you sure you want to permanently delete this
|
||||
user?
|
||||
permanently_delete_confirm: Are you sure you want to permanently delete this user?
|
||||
remove_avatar: Remove avatar
|
||||
save_changes: Save changes
|
||||
unlock: Unlock
|
||||
@@ -148,6 +155,11 @@ en:
|
||||
commented_on: Commented on
|
||||
mention:
|
||||
mentioned_you_on: Mentioned you on
|
||||
passwords:
|
||||
flashes:
|
||||
successfully_update: Successfully update
|
||||
token_invalid: Token invalid
|
||||
user_email_not_found: User email not found
|
||||
sessions:
|
||||
access_limiter:
|
||||
are_you: Are you
|
||||
@@ -191,6 +203,11 @@ en:
|
||||
account: Account
|
||||
password: Password
|
||||
profile: Profile
|
||||
share:
|
||||
user_confirm_required:
|
||||
change_email: Change email
|
||||
description: "Please confirm your email for access all function. Confirm email has been send to %{email} ."
|
||||
resend_email: Resend email
|
||||
subscriptions:
|
||||
subscription:
|
||||
ignoring: Ignoring
|
||||
@@ -200,10 +217,8 @@ en:
|
||||
watch: Watch
|
||||
watching: Watching
|
||||
you_do_not_receive_any_notifications: You do not receive any notifications
|
||||
you_only_receive_notifications_if_you_are_mentioned: You only receive notifications
|
||||
if you are mentioned
|
||||
you_will_receive_notifications_for_all_comments: You will receive notifications
|
||||
for all comments
|
||||
you_only_receive_notifications_if_you_are_mentioned: You only receive notifications if you are mentioned
|
||||
you_will_receive_notifications_for_all_comments: You will receive notifications for all comments
|
||||
topics:
|
||||
edit:
|
||||
edit_topic: Edit topic
|
||||
@@ -242,34 +257,23 @@ en:
|
||||
topics_header:
|
||||
create_topic: Create topic
|
||||
user_mailer:
|
||||
confirmation:
|
||||
subject: Email confirmation
|
||||
text_body: |
|
||||
Somebody sign up in %{host} with your email. If it was not your, safely ignore this email.
|
||||
|
||||
Click the following link to choose a new password:
|
||||
|
||||
%{url}
|
||||
password_reset:
|
||||
subject: Password reset
|
||||
text_body: |
|
||||
Somebody asked to reset your password on %{host} .
|
||||
|
||||
If it was not you, you can safely ignore this email.
|
||||
Somebody asked to reset your password on %{host}. If it was not you, safely ignore this email.
|
||||
|
||||
Click the following link to choose a new password:
|
||||
|
||||
%{url}
|
||||
users:
|
||||
passwords:
|
||||
edit:
|
||||
password_reset: Password reset
|
||||
save_changes: Save changes
|
||||
flashes:
|
||||
successfully_update: Successfully update
|
||||
token_invalid: Token invalid
|
||||
user_email_not_found: User email not found
|
||||
new:
|
||||
email: Email
|
||||
password_reset: Password reset
|
||||
send_reset_email: Send reset email
|
||||
your_email: Your email
|
||||
show:
|
||||
password_reset: Password reset
|
||||
password_reset_email_has_been_sent_message: Password reset email has been sent
|
||||
message
|
||||
comments:
|
||||
index:
|
||||
comments: Comments
|
||||
@@ -277,6 +281,15 @@ en:
|
||||
no_comment_yet: No comment yet
|
||||
publish: Publish
|
||||
user_s_comments: "%{name}'s comments"
|
||||
confirmations:
|
||||
confirm_success: Confirm success
|
||||
create:
|
||||
resend_success: Resend success
|
||||
limiter:
|
||||
resend_limiter: Resend limiter
|
||||
show:
|
||||
confirm_fail: Confirm fail
|
||||
confirmation_toke_invalid_or_expired_please_resend: Confirmation toke invalid or expired, please resend.
|
||||
new:
|
||||
choose_a_password: Choose a password
|
||||
create_account: Create account
|
||||
@@ -284,6 +297,18 @@ en:
|
||||
sign_up: Sign up
|
||||
your_email: Your email
|
||||
your_full_name: Your full name
|
||||
passwords:
|
||||
edit:
|
||||
password_reset: Password reset
|
||||
save_changes: Save changes
|
||||
new:
|
||||
email: Email
|
||||
password_reset: Password reset
|
||||
send_reset_email: Send reset email
|
||||
your_email: Your email
|
||||
show:
|
||||
password_reset: Password reset
|
||||
password_reset_email_has_been_sent_message: Password reset email has been sent.
|
||||
profile:
|
||||
edit_profile: Edit profile
|
||||
sub_navbar:
|
||||
|
||||
@@ -155,6 +155,11 @@ zh-CN:
|
||||
commented_on: "评论了"
|
||||
mention:
|
||||
mentioned_you_on: "提及你于"
|
||||
passwords:
|
||||
flashes:
|
||||
successfully_update: "密码更新成功"
|
||||
token_invalid: "验证码无效"
|
||||
user_email_not_found: "没有找到此用户"
|
||||
sessions:
|
||||
access_limiter:
|
||||
are_you: "你是否"
|
||||
@@ -198,6 +203,11 @@ zh-CN:
|
||||
account: "帐号"
|
||||
password: "密码"
|
||||
profile: "个人资料"
|
||||
share:
|
||||
user_confirm_required:
|
||||
change_email: "修改邮件地址"
|
||||
description: "确认你的邮件地址方可访问所有功能。确认邮件已发往 %{email} 。"
|
||||
resend_email: "重新发送邮件"
|
||||
subscriptions:
|
||||
subscription:
|
||||
ignoring: "忽略"
|
||||
@@ -247,33 +257,23 @@ zh-CN:
|
||||
topics_header:
|
||||
create_topic: "创建话题"
|
||||
user_mailer:
|
||||
confirmation:
|
||||
subject: "邮箱确认"
|
||||
text_body: |
|
||||
有人使用你的邮箱在 %{host} 注册帐号,如果不是你,请忽略此邮件。
|
||||
|
||||
点击下面的链接确认邮箱地址:
|
||||
|
||||
%{url}
|
||||
password_reset:
|
||||
subject: "密码重置"
|
||||
text_body: |
|
||||
有人请求重置你在 %{host} 的密码。
|
||||
|
||||
如果不是你,你可以直接忽略本邮件。
|
||||
有人请求重置你在 %{host} 的密码,如果不是你,请忽略此邮件。
|
||||
|
||||
点击下面的链接来选择一个新密码:
|
||||
|
||||
%{url}
|
||||
users:
|
||||
passwords:
|
||||
edit:
|
||||
password_reset: "密码重置"
|
||||
save_changes: "保存修改"
|
||||
flashes:
|
||||
successfully_update: "成功更新,现在可以使用新密码登录!"
|
||||
token_invalid: "密码重置地址过期或无效,请重新发送重置邮件。"
|
||||
user_email_not_found: "没有找到此用户。"
|
||||
new:
|
||||
email: Email
|
||||
password_reset: "密码重置"
|
||||
send_reset_email: "发送重置密码"
|
||||
your_email: "你的 Email 地址"
|
||||
show:
|
||||
password_reset: "密码重置"
|
||||
password_reset_email_has_been_sent_message: "密码重置邮件已发送,请注意查收。"
|
||||
comments:
|
||||
index:
|
||||
comments: "评论"
|
||||
@@ -281,6 +281,15 @@ zh-CN:
|
||||
no_comment_yet: "还没有评论"
|
||||
publish: "发布"
|
||||
user_s_comments: "%{name} 的评论"
|
||||
confirmations:
|
||||
confirm_success: "恭喜,你的邮件地址已确认成功。"
|
||||
create:
|
||||
resend_success: "确认邮件已发送。"
|
||||
limiter:
|
||||
resend_limiter: "一分钟只能发送一次确认邮件。"
|
||||
show:
|
||||
confirm_fail: "确认失败"
|
||||
confirmation_toke_invalid_or_expired_please_resend: "确认码无效或已过期,请重新发送确认邮件。"
|
||||
likes:
|
||||
index:
|
||||
no_comment_yet: "还没有评论"
|
||||
@@ -291,6 +300,18 @@ zh-CN:
|
||||
sign_up: "注册"
|
||||
your_email: "你的邮箱地址"
|
||||
your_full_name: "你的全名"
|
||||
passwords:
|
||||
edit:
|
||||
password_reset: "密码重置"
|
||||
save_changes: "保存修改"
|
||||
new:
|
||||
email: Email
|
||||
password_reset: "密码重置"
|
||||
send_reset_email: "发送重置密码"
|
||||
your_email: "你的 Email 地址"
|
||||
show:
|
||||
password_reset: "密码重置"
|
||||
password_reset_email_has_been_sent_message: "密码重置邮件已发送,请注意查收。"
|
||||
profile:
|
||||
edit_profile: "编辑个人资料"
|
||||
sub_navbar:
|
||||
|
||||
@@ -24,6 +24,7 @@ Rails.application.routes.draw do
|
||||
|
||||
namespace :users do
|
||||
resource :password, only: [:show, :new, :create, :edit, :update]
|
||||
resource :confirmation, only: [:show, :create]
|
||||
end
|
||||
|
||||
concern :commentable do
|
||||
|
||||
5
db/migrate/20140412065000_add_confirmed_to_users.rb
Normal file
5
db/migrate/20140412065000_add_confirmed_to_users.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddConfirmedToUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :confirmed, :boolean, default: false
|
||||
end
|
||||
end
|
||||
@@ -295,7 +295,8 @@ CREATE TABLE users (
|
||||
locale character varying(255),
|
||||
locked_at timestamp without time zone,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
updated_at timestamp without time zone,
|
||||
confirmed boolean DEFAULT false
|
||||
);
|
||||
|
||||
|
||||
@@ -576,3 +577,5 @@ INSERT INTO schema_migrations (version) VALUES ('20140310070632');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20140405074043');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20140412065000');
|
||||
|
||||
|
||||
48
test/controllers/users/confirmations_controller_test.rb
Normal file
48
test/controllers/users/confirmations_controller_test.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
require 'test_helper'
|
||||
|
||||
class Users::ConfirmationsControllerTest < ActionController::TestCase
|
||||
def setup
|
||||
$redis.flushdb
|
||||
end
|
||||
|
||||
test "should confirm user if token valid" do
|
||||
login_as create(:user, confirmed: false)
|
||||
get :show, token: current_user.confirmation_token
|
||||
assert current_user.reload.confirmed?
|
||||
end
|
||||
|
||||
test "should not confirm user if token invalid" do
|
||||
login_as create(:user, confirmed: false)
|
||||
get :show, token: current_user.confirmation_token[0..-2]
|
||||
assert !current_user.reload.confirmed?
|
||||
end
|
||||
|
||||
test "should show" do
|
||||
login_as create(:user, confirmed: false)
|
||||
get :show
|
||||
assert_response :success, @response.body
|
||||
end
|
||||
|
||||
test "should create confirm" do
|
||||
login_as create(:user, confirmed: false)
|
||||
xhr :post, :create
|
||||
assert ActionMailer::Base.deliveries.any?
|
||||
end
|
||||
|
||||
test "should access limit" do
|
||||
login_as create(:user)
|
||||
ip = '1.2.3.4'
|
||||
key = "verifies:limiter:#{ip}"
|
||||
request.headers['REMOTE_ADDR'] = ip
|
||||
assert_equal nil, $redis.get(key)
|
||||
assert_difference "ActionMailer::Base.deliveries.count" do
|
||||
xhr :post, :create
|
||||
assert_equal 1, $redis.get(key).to_i
|
||||
end
|
||||
|
||||
assert_no_difference "ActionMailer::Base.deliveries.count" do
|
||||
xhr :post, :create
|
||||
assert_equal 1, $redis.get(key).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,4 +4,9 @@ class UserMailerPreview < ActionMailer::Preview
|
||||
user = User.first || FactoryGirl.create(:user)
|
||||
UserMailer.password_reset(user.id)
|
||||
end
|
||||
|
||||
def confirmation
|
||||
user = User.first || FactoryGirl.create(:user)
|
||||
UserMailer.confirmation(user.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
require 'test_helper'
|
||||
|
||||
class UserMailerTest < ActionMailer::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
test "confirmation" do
|
||||
user = create(:user)
|
||||
email = UserMailer.confirmation(user.id).deliver
|
||||
assert ActionMailer::Base.deliveries.any?
|
||||
assert_equal [user.email], email.to
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,4 +22,15 @@ class UserTest < ActiveSupport::TestCase
|
||||
token = user.password_reset_token
|
||||
assert_equal user, User.find_by_password_reset_token(token)
|
||||
end
|
||||
|
||||
test "shuold generate confirmation token" do
|
||||
user = create(:user)
|
||||
assert_not_nil user.confirmation_token
|
||||
end
|
||||
|
||||
test "should find_by_confirmation_token" do
|
||||
user = create(:user)
|
||||
token = user.confirmation_token
|
||||
assert_equal user, User.find_by_confirmation_token(token)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user