mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-30 10:58:31 -05:00
Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
7d7d8c7d4f
16 changed files with 127 additions and 13 deletions
|
@ -14,6 +14,6 @@
|
||||||
|
|
||||||
* Nick Sahler - UI Implementation, supplemental
|
* Nick Sahler - UI Implementation, supplemental
|
||||||
|
|
||||||
* Don Petersen - Ruby developmer, installation scripts
|
* Don Petersen - Ruby developer, installation scripts
|
||||||
|
|
||||||
*For a more detailed list of the many individuals that contributed to the design and development of Discourse outside of GitHub, please refer to the official Discourse website.*
|
*For a more detailed list of the many individuals that contributed to the design and development of Discourse outside of GitHub, please refer to the official Discourse website.*
|
||||||
|
|
12
README.md
12
README.md
|
@ -15,7 +15,17 @@ Whenever you need ...
|
||||||
|
|
||||||
If you're interested in helping us develop Discourse, please start with our **[Discourse Developer Install Guide](https://github.com/discourse/discourse/blob/master/DEVELOPMENT.md)**, which includes instructions to get up and running in a development environment.
|
If you're interested in helping us develop Discourse, please start with our **[Discourse Developer Install Guide](https://github.com/discourse/discourse/blob/master/DEVELOPMENT.md)**, which includes instructions to get up and running in a development environment.
|
||||||
|
|
||||||
We also have a **[Discourse "Quick-and-Dirty" Install Guide](https://github.com/discourse/discourse/blob/master/INSTALL.md)**.
|
### The quick and easy setup
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@github.com:discourse/discourse.git
|
||||||
|
cd discourse
|
||||||
|
rake db:create
|
||||||
|
rake db:migrate
|
||||||
|
rake db:seed_fu
|
||||||
|
redis-cli flushall
|
||||||
|
thin start
|
||||||
|
```
|
||||||
|
|
||||||
## Vision
|
## Vision
|
||||||
|
|
||||||
|
|
|
@ -190,9 +190,9 @@ window.Discourse.User.reopenClass
|
||||||
error: (xhr) -> promise.reject(xhr)
|
error: (xhr) -> promise.reject(xhr)
|
||||||
promise
|
promise
|
||||||
|
|
||||||
createAccount: (name, email, password, username) ->
|
createAccount: (name, email, password, username, passwordConfirm, challenge) ->
|
||||||
$.ajax
|
$.ajax
|
||||||
url: '/users'
|
url: '/users'
|
||||||
dataType: 'json'
|
dataType: 'json'
|
||||||
data: {name: name, email: email, password: password, username: username}
|
data: {name: name, email: email, password: password, username: username, password_confirmation: passwordConfirm, challenge: challenge}
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
|
|
|
@ -49,6 +49,14 @@
|
||||||
</tr>
|
</tr>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<tr class="password-confirmation">
|
||||||
|
<td><label for='new-account-password-confirmation'>Password Again</label></td>
|
||||||
|
<td>
|
||||||
|
{{view Ember.TextField valueBinding="view.accountPasswordConfirm" type="password" id="new-account-password-confirmation"}}
|
||||||
|
{{view Ember.TextField valueBinding="view.accountChallenge" id="new-account-challenge"}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,8 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco
|
||||||
title: Em.String.i18n('create_account.title')
|
title: Em.String.i18n('create_account.title')
|
||||||
uniqueUsernameValidation: null
|
uniqueUsernameValidation: null
|
||||||
complete: false
|
complete: false
|
||||||
|
accountPasswordConfirm: 0
|
||||||
|
accountChallenge: 0
|
||||||
|
|
||||||
|
|
||||||
submitDisabled: (->
|
submitDisabled: (->
|
||||||
|
@ -22,6 +24,8 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco
|
||||||
# If blank, fail without a reason
|
# If blank, fail without a reason
|
||||||
return Discourse.InputValidation.create(failed: true) if @blank('accountName')
|
return Discourse.InputValidation.create(failed: true) if @blank('accountName')
|
||||||
|
|
||||||
|
@fetchConfirmationValue() if @get('accountPasswordConfirm') == 0
|
||||||
|
|
||||||
# If too short
|
# If too short
|
||||||
return Discourse.InputValidation.create(failed: true, reason: Em.String.i18n('user.name.too_short')) if @get('accountName').length < 3
|
return Discourse.InputValidation.create(failed: true, reason: Em.String.i18n('user.name.too_short')) if @get('accountName').length < 3
|
||||||
|
|
||||||
|
@ -120,13 +124,22 @@ window.Discourse.CreateAccountView = window.Discourse.ModalBodyView.extend Disco
|
||||||
).property('accountPassword')
|
).property('accountPassword')
|
||||||
|
|
||||||
|
|
||||||
|
fetchConfirmationValue: ->
|
||||||
|
$.ajax
|
||||||
|
url: '/users/hp.json',
|
||||||
|
success: (json) =>
|
||||||
|
@set('accountPasswordConfirm', json.value)
|
||||||
|
@set('accountChallenge', json.challenge.split("").reverse().join(""))
|
||||||
|
|
||||||
createAccount: ->
|
createAccount: ->
|
||||||
name = @get('accountName')
|
name = @get('accountName')
|
||||||
email = @get('accountEmail')
|
email = @get('accountEmail')
|
||||||
password = @get('accountPassword')
|
password = @get('accountPassword')
|
||||||
username = @get('accountUsername')
|
username = @get('accountUsername')
|
||||||
|
passwordConfirm = @get('accountPasswordConfirm')
|
||||||
|
challenge = @get('accountChallenge')
|
||||||
|
|
||||||
Discourse.User.createAccount(name, email, password, username).then (result) =>
|
Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then (result) =>
|
||||||
|
|
||||||
if result.success
|
if result.success
|
||||||
@flash(result.message)
|
@flash(result.message)
|
||||||
|
|
|
@ -152,6 +152,9 @@
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.password-confirmation {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#move-selected {
|
#move-selected {
|
||||||
|
|
|
@ -123,6 +123,12 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
||||||
|
if params[:password_confirmation] != honeypot_value or params[:challenge] != challenge_value.try(:reverse)
|
||||||
|
# Don't give any indication that we caught you in the honeypot
|
||||||
|
return render(:json => {success: true, active: false, message: I18n.t("login.activate_email", email: params[:email]) })
|
||||||
|
end
|
||||||
|
|
||||||
user = User.new
|
user = User.new
|
||||||
user.name = params[:name]
|
user.name = params[:name]
|
||||||
user.email = params[:email]
|
user.email = params[:email]
|
||||||
|
@ -183,6 +189,10 @@ class UsersController < ApplicationController
|
||||||
render json: {errors: [I18n.t("mothership.access_token_problem")]}
|
render json: {errors: [I18n.t("mothership.access_token_problem")]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_honeypot_value
|
||||||
|
render json: {value: honeypot_value, challenge: challenge_value}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# all avatars are funneled through here
|
# all avatars are funneled through here
|
||||||
def avatar
|
def avatar
|
||||||
|
@ -320,6 +330,14 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def honeypot_value
|
||||||
|
Digest::SHA1::hexdigest("#{Discourse.current_hostname}:#{Discourse::Application.config.secret_token}")[0,15]
|
||||||
|
end
|
||||||
|
|
||||||
|
def challenge_value
|
||||||
|
'3019774c067cc2b'
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_user_from_params
|
def fetch_user_from_params
|
||||||
username_lower = params[:username].downcase
|
username_lower = params[:username].downcase
|
||||||
username_lower.gsub!(/\.json$/, '')
|
username_lower.gsub!(/\.json$/, '')
|
||||||
|
|
|
@ -2,6 +2,8 @@ require_dependency 'rate_limiter'
|
||||||
require_dependency 'system_message'
|
require_dependency 'system_message'
|
||||||
|
|
||||||
class PostAction < ActiveRecord::Base
|
class PostAction < ActiveRecord::Base
|
||||||
|
class AlreadyFlagged < StandardError; end
|
||||||
|
|
||||||
include RateLimiter::OnCreateRecord
|
include RateLimiter::OnCreateRecord
|
||||||
|
|
||||||
attr_accessible :deleted_at, :post_action_type_id, :post_id, :user_id, :post, :user, :post_action_type, :message
|
attr_accessible :deleted_at, :post_action_type_id, :post_id, :user_id, :post, :user, :post_action_type, :message
|
||||||
|
@ -115,6 +117,15 @@ class PostAction < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before_create do
|
||||||
|
if is_flag?
|
||||||
|
if PostAction.where('user_id = ? and post_id = ? and post_action_type_id in (?) and deleted_at is null',
|
||||||
|
self.user_id, self.post_id, PostActionType.FlagTypes).exists?
|
||||||
|
raise AlreadyFlagged
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
after_save do
|
after_save do
|
||||||
|
|
||||||
# Update denormalized counts
|
# Update denormalized counts
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class PostActionType < ActiveRecord::Base
|
class PostActionType < ActiveRecord::Base
|
||||||
|
|
||||||
attr_accessible :id, :is_flag, :name_key, :icon
|
attr_accessible :id, :is_flag, :name_key, :icon
|
||||||
|
|
||||||
def self.ordered
|
def self.ordered
|
||||||
|
|
|
@ -93,10 +93,6 @@ module Discourse
|
||||||
# So open id logs somewhere sane
|
# So open id logs somewhere sane
|
||||||
config.after_initialize do
|
config.after_initialize do
|
||||||
OpenID::Util.logger = Rails.logger
|
OpenID::Util.logger = Rails.logger
|
||||||
|
|
||||||
# latest possible so earliest in the stack
|
|
||||||
# require 'rack/message_bus'
|
|
||||||
# config.middleware.insert(0, Rack::MessageBus)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,6 +80,7 @@ Discourse::Application.routes.draw do
|
||||||
put 'users/password-reset/:token' => 'users#password_reset'
|
put 'users/password-reset/:token' => 'users#password_reset'
|
||||||
get 'users/activate-account/:token' => 'users#activate_account'
|
get 'users/activate-account/:token' => 'users#activate_account'
|
||||||
get 'users/authorize-email/:token' => 'users#authorize_email'
|
get 'users/authorize-email/:token' => 'users#authorize_email'
|
||||||
|
get 'users/hp' => 'users#get_honeypot_value'
|
||||||
|
|
||||||
get 'user_preferences' => 'users#user_preferences_redirect'
|
get 'user_preferences' => 'users#user_preferences_redirect'
|
||||||
get 'users/:username/private-messages' => 'user_actions#private_messages', :format => false, :constraints => {:username => USERNAME_ROUTE_FORMAT}
|
get 'users/:username/private-messages' => 'user_actions#private_messages', :format => false, :constraints => {:username => USERNAME_ROUTE_FORMAT}
|
||||||
|
|
|
@ -27,6 +27,13 @@ PostActionType.seed do |s|
|
||||||
s.position = 4
|
s.position = 4
|
||||||
end
|
end
|
||||||
|
|
||||||
|
PostActionType.seed do |s|
|
||||||
|
s.id = PostActionType.Types[:vote]
|
||||||
|
s.name_key = 'vote'
|
||||||
|
s.is_flag = false
|
||||||
|
s.position = 5
|
||||||
|
end
|
||||||
|
|
||||||
PostActionType.seed do |s|
|
PostActionType.seed do |s|
|
||||||
s.id = PostActionType.Types[:spam]
|
s.id = PostActionType.Types[:spam]
|
||||||
s.name_key = 'spam'
|
s.name_key = 'spam'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class AddMetaDataToForumThreads < ActiveRecord::Migration
|
class AddMetaDataToForumThreads < ActiveRecord::Migration
|
||||||
def change
|
def change
|
||||||
execute "CREATE EXTENSION hstore"
|
execute "CREATE EXTENSION IF NOT EXISTS hstore"
|
||||||
add_column :forum_threads, :meta_data, :hstore
|
add_column :forum_threads, :meta_data, :hstore
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,8 +31,8 @@ module SiteSettingExtension
|
||||||
end
|
end
|
||||||
|
|
||||||
# just like a setting, except that it is available in javascript via DiscourseSession
|
# just like a setting, except that it is available in javascript via DiscourseSession
|
||||||
def client_setting(name, defualt = nil, type = nil)
|
def client_setting(name, default = nil, type = nil)
|
||||||
setting(name,defualt,type)
|
setting(name,default,type)
|
||||||
@@client_settings ||= []
|
@@client_settings ||= []
|
||||||
@@client_settings << name
|
@@client_settings << name
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,11 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe UsersController do
|
describe UsersController do
|
||||||
|
|
||||||
|
before do
|
||||||
|
UsersController.any_instance.stubs(:honeypot_value).returns(nil)
|
||||||
|
UsersController.any_instance.stubs(:challenge_value).returns(nil)
|
||||||
|
end
|
||||||
|
|
||||||
describe '.show' do
|
describe '.show' do
|
||||||
let!(:user) { log_in }
|
let!(:user) { log_in }
|
||||||
|
|
||||||
|
@ -340,6 +345,40 @@ describe UsersController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples_for 'honeypot fails' do
|
||||||
|
it 'should not create a new user' do
|
||||||
|
expect {
|
||||||
|
xhr :post, :create, create_params
|
||||||
|
}.to_not change { User.count }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should not send an email' do
|
||||||
|
User.any_instance.expects(:enqueue_welcome_message).never
|
||||||
|
xhr :post, :create, create_params
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should say it was successful' do
|
||||||
|
xhr :post, :create, create_params
|
||||||
|
json = JSON::parse(response.body)
|
||||||
|
json["success"].should be_true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when honeypot value is wrong' do
|
||||||
|
before do
|
||||||
|
UsersController.any_instance.stubs(:honeypot_value).returns('abc')
|
||||||
|
end
|
||||||
|
let(:create_params) { {:name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email, :password_confirmation => 'wrong'} }
|
||||||
|
it_should_behave_like 'honeypot fails'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when challenge answer is wrong' do
|
||||||
|
before do
|
||||||
|
UsersController.any_instance.stubs(:challenge_value).returns('abc')
|
||||||
|
end
|
||||||
|
let(:create_params) { {:name => @user.name, :username => @user.username, :password => "strongpassword", :email => @user.email, :challenge => 'abc'} }
|
||||||
|
it_should_behave_like 'honeypot fails'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.username' do
|
context '.username' do
|
||||||
|
|
|
@ -111,6 +111,13 @@ describe PostAction do
|
||||||
|
|
||||||
describe 'flagging' do
|
describe 'flagging' do
|
||||||
|
|
||||||
|
it 'does not allow you to flag stuff with 2 reasons' do
|
||||||
|
post = Fabricate(:post)
|
||||||
|
u1 = Fabricate(:evil_trout)
|
||||||
|
PostAction.act(u1, post, PostActionType.Types[:spam])
|
||||||
|
lambda { PostAction.act(u1, post, PostActionType.Types[:off_topic]) }.should raise_error(PostAction::AlreadyFlagged)
|
||||||
|
end
|
||||||
|
|
||||||
it 'should update counts when you clear flags' do
|
it 'should update counts when you clear flags' do
|
||||||
post = Fabricate(:post)
|
post = Fabricate(:post)
|
||||||
u1 = Fabricate(:evil_trout)
|
u1 = Fabricate(:evil_trout)
|
||||||
|
|
Loading…
Reference in a new issue