Add user confirmation module

This commit is contained in:
Rei
2014-04-12 16:24:11 +08:00
parent e590168558
commit 10bc72acc2
22 changed files with 286 additions and 53 deletions

View File

@@ -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;

View 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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -72,4 +72,6 @@ html
a href=topics_path
= t '.community'
- if login? and !current_user.confirmed?
= render 'share/user_confirm_required'
= yield

View 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"
| &times;
.modal-title

View File

@@ -0,0 +1 @@
= sanitize markdown t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host']

View File

@@ -0,0 +1 @@
<%= t '.text_body', url: users_confirmation_url(token: @user.confirmation_token), host: CONFIG['host'] %>

View File

@@ -0,0 +1 @@
$('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_success' %>').end().modal();

View File

@@ -0,0 +1 @@
$('#user-confirm-required-modal').find('.modal-title').text('<%= t '.resend_limiter' %>').end().modal();

View 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'

View File

@@ -6,6 +6,7 @@ search:
- '*.rb'
- '*.slim'
- '*.text.erb'
- '*.js.erb'
ignore_unused:
- activerecord.*

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -0,0 +1,5 @@
class AddConfirmedToUsers < ActiveRecord::Migration
def change
add_column :users, :confirmed, :boolean, default: false
end
end

View File

@@ -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');

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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