diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index fed6ec280..f515d386d 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -73,15 +73,21 @@ class AccountsController < ApplicationController def login @user = User.try_to_login(params[:login], params[:password]) - if @user - # user is already in local database - return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? - return normal_status(-2, "错误的账号或密码") unless @user.check_password?(params[:password].to_s) - else + return normal_status(-2, "错误的账号或密码") if @user.blank? + # user is already in local database + return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked? + + login_control = LimitForbidControl::UserLogin.new(@user) + return normal_status(-2, "登录密码出错已达上限,将锁定密码1小时") if login_control.forbid? + + password_ok = @user.check_password?(params[:password].to_s) + unless password_ok + login_control.increment! return normal_status(-2, "错误的账号或密码") end successful_authentication(@user) + login_control.clear # 重置每日密码错误次数 session[:user_id] = @user.id end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 76ffe23d4..6ad488f48 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -86,8 +86,18 @@ class ApplicationController < ActionController::Base when 8, 3, 5 # 邮箱类型的发送 sigle_para = {email: value} + # 60s内不能重复发送 + send_email_limit_cache_key = "send_email_60_second_limit:#{value}" + tip_exception(-2, '请勿频繁操作') if Rails.cache.exist?(send_email_limit_cache_key) + + # 短时间内不能大量发送 + send_email_control = LimitForbidControl::SendEmailCode.new(value) + tip_exception(-2, '邮件发送太频繁,请稍后再试') if send_email_control.forbid? begin UserMailer.register_email(value, code).deliver_now + + Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute) + send_email_control.increment! # Mailer.run.email_register(code, value) rescue Exception => e logger_error(e) diff --git a/app/libs/limit_forbid_control.rb b/app/libs/limit_forbid_control.rb new file mode 100644 index 000000000..f45a53130 --- /dev/null +++ b/app/libs/limit_forbid_control.rb @@ -0,0 +1,2 @@ +module LimitForbidControl +end \ No newline at end of file diff --git a/app/libs/limit_forbid_control/base.rb b/app/libs/limit_forbid_control/base.rb new file mode 100644 index 000000000..a437f48b6 --- /dev/null +++ b/app/libs/limit_forbid_control/base.rb @@ -0,0 +1,56 @@ +class LimitForbidControl::Base + def initialize + end + + def cache_key + raise 'Please overwrite method :cache_Key' + end + + def forbid_cache_key + "#{cache_key}:forbid" + end + + def allow_times + 5 + end + + def cumulative_expires + 1.days + end + + def forbid_expires + 1.hours + end + + def forbid? + Rails.cache.read(forbid_cache_key) + end + + def increment! + value = Rails.cache.read(cache_key) + value = value.to_i + 1 + + # 锁定 + if value > allow_times.to_i + Rails.cache.write(forbid_cache_key, true, expires_in: forbid_expires) + Rails.cache.delete(cache_key) + else + Rails.cache.write(cache_key, value, expires_in: cumulative_expires) + end + end + + def clear + Rails.cache.delete(forbid_cache_key) + Rails.cache.delete(cache_key) + end + + private + + def redis_cache? + Rails.cache.is_a?(ActiveSupport::Cache::RedisStore) + end + + def day + Time.current.strftime('%Y%m%d') + end +end \ No newline at end of file diff --git a/app/libs/limit_forbid_control/send_email_code.rb b/app/libs/limit_forbid_control/send_email_code.rb new file mode 100644 index 000000000..729446e7c --- /dev/null +++ b/app/libs/limit_forbid_control/send_email_code.rb @@ -0,0 +1,25 @@ +class LimitForbidControl::SendEmailCode < LimitForbidControl::Base + attr_reader :email + + def initialize(email) + super() + @email = email + end + + def allow_times + EduSetting.get('daily_send_email_code_times').presence || 5 + end + + def forbid_expires + num = EduSetting.get('daily_send_email_code_forbid_time').presence.to_i + num.zero? ? 10.minutes : num.to_i.hours + end + + def cumulative_expires + 1.hours + end + + def cache_key + @_cache_key ||= "limit_forbid_control:#{day}:send_email_code:#{email}" + end +end \ No newline at end of file diff --git a/app/libs/limit_forbid_control/user_login.rb b/app/libs/limit_forbid_control/user_login.rb new file mode 100644 index 000000000..882556eb1 --- /dev/null +++ b/app/libs/limit_forbid_control/user_login.rb @@ -0,0 +1,25 @@ +class LimitForbidControl::UserLogin < LimitForbidControl::Base + attr_reader :user + + def initialize(user) + super() + @user = user + end + + def allow_times + EduSetting.get('daily_error_password_times').presence || 5 + end + + def forbid_expires + num = EduSetting.get('daily_error_password_forbid_time').presence.to_i + num.zero? ? 1.hours : num.to_i.hours + end + + def cumulative_expires + 1.days + end + + def cache_key + @_cache_key ||= "limit_forbid_control:#{day}:user_login:#{user.id}" + end +end \ No newline at end of file diff --git a/public/react/src/common/UrlTool.js b/public/react/src/common/UrlTool.js index 3ad52f264..db97642b7 100644 --- a/public/react/src/common/UrlTool.js +++ b/public/react/src/common/UrlTool.js @@ -71,3 +71,18 @@ export function toPath(path) { export function getTaskUrlById(id) { return `/tasks/${id}` } + + +export function htmlEncode(str) { + var s = ""; + if (str.length === 0) { + return ""; + } + s = str.replace(/&/g, "&"); + s = s.replace(//g, ">"); + s = s.replace(/ /g, " "); + s = s.replace(/\'/g, "'");//IE下不支持实体名称 + s = s.replace(/\"/g, """); + return s; +} \ No newline at end of file diff --git a/public/react/src/common/educoder.js b/public/react/src/common/educoder.js index ec4659de7..73c707c9b 100644 --- a/public/react/src/common/educoder.js +++ b/public/react/src/common/educoder.js @@ -4,7 +4,7 @@ import { from } from '_array-flatten@2.1.2@array-flatten'; export { getImageUrl as getImageUrl, getUrl as getUrl, getUrl2 as getUrl2, setImagesUrl as setImagesUrl , getUploadActionUrl as getUploadActionUrl, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth - , getTaskUrlById as getTaskUrlById, TEST_HOST } from './UrlTool'; + , getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode } from './UrlTool'; export { default as queryString } from './UrlTool2'; export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC'; diff --git a/public/react/src/modules/forums/MemoDetail.js b/public/react/src/modules/forums/MemoDetail.js index cc206713b..7578eea6e 100644 --- a/public/react/src/modules/forums/MemoDetail.js +++ b/public/react/src/modules/forums/MemoDetail.js @@ -22,7 +22,7 @@ import {ImageLayerOfCommentHOC} from '../page/layers/ImageLayerOfCommentHOC' import MemoDetailKEEditor from './MemoDetailKEEditor' import MemoDetailMDEditor from './MemoDetailMDEditor' -import { bytesToSize, CBreadcrumb } from 'educoder' +import { bytesToSize, CBreadcrumb ,htmlEncode} from 'educoder' import { Tooltip } from 'antd' // import CBreadcrumb from '../courses/common/CBreadcrumb' @@ -246,6 +246,8 @@ class MemoDetail extends Component { if (commentContent) { commentContent = commentContent.replace(/(\n
\n\t
\n<\/p>)*$/g,'');
}
+
+ commentContent=htmlEncode(commentContent)
axios.post(url, {
parent_id: id,
content: commentContent
@@ -491,6 +493,7 @@ class MemoDetail extends Component {
const url = `/memos/reply.json`;
let { comments } = this.state;
const user = this._getUser();
+ content=htmlEncode(content)
axios.post(url, {
parent_id: memo.id,
content: content
diff --git a/public/react/src/modules/message/js/MessagChat.js b/public/react/src/modules/message/js/MessagChat.js
index 34494a256..e320ff3a3 100644
--- a/public/react/src/modules/message/js/MessagChat.js
+++ b/public/react/src/modules/message/js/MessagChat.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import "../css/messagemy.css"
-import {getImageUrl,markdownToHTML} from 'educoder';
+import {getImageUrl,markdownToHTML,htmlEncode} from 'educoder';
import { Modal,Input,Icon,Tooltip,Spin} from 'antd';
import axios from 'axios';
import TPMMDEditor from '../../tpm/challengesnew/TPMMDEditor';
@@ -417,6 +417,7 @@ class MessagChat extends Component{
let contents=this.messageRef.current.getValue().trim();
const query = this.props.location.search;
let target_ids = query.split('?target_ids=');
+ contents=htmlEncode(contents)
let url = `/users/${this.props.match.params.userid}/private_messages.json`;
axios.post(url, {
target_id: target_ids[1],
diff --git a/public/react/src/modules/message/messagemodal/WriteaprivateletterModal.js b/public/react/src/modules/message/messagemodal/WriteaprivateletterModal.js
index dfa27ba9a..da0af0448 100644
--- a/public/react/src/modules/message/messagemodal/WriteaprivateletterModal.js
+++ b/public/react/src/modules/message/messagemodal/WriteaprivateletterModal.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Modal,Input,Icon,Tooltip,Spin} from 'antd';
import axios from 'axios';
// import '../../modules/user/common.css';
-import {getImageUrl} from 'educoder';
+import {getImageUrl,htmlEncode} from 'educoder';
//完善个人资料
class WriteaprivateletterModal extends Component {
@@ -58,6 +58,7 @@ class WriteaprivateletterModal extends Component {
//发送私信
SendprivatemessageAPI=(idvalue,contentvalue)=>{
const url =`/users/${this.props.current_user.user_id}/private_messages.json`
+ contentvalue=htmlEncode(contentvalue)
let data={
target_id:idvalue,
content:contentvalue,