From 5d2fc6de321ac556ded72e5a8fa9fcf47d172e16 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 24 Dec 2018 19:12:38 +0100 Subject: Add REST API for creating an account (#9572) * Add REST API for creating an account The method is available to apps with a token obtained via the client credentials grant. It creates a user and account records, as well as an access token for the app that initiated the request. The user is unconfirmed, and an e-mail is sent as usual. The method returns the access token, which the app should save for later. The REST API is not available to users with unconfirmed accounts, so the app must be smart to wait for the user to click a link in their e-mail inbox. The method is rate-limited by IP to 5 requests per 30 minutes. * Redirect users back to app from confirmation if they were created with an app * Add tests * Return 403 on the method if registrations are not open * Require agreement param to be true in the API when creating an account --- .../controllers/api/v1/accounts_controller_spec.rb | 34 ++++++++++++++++++ spec/fabricators/user_fabricator.rb | 1 + spec/models/user_spec.rb | 14 ++++---- spec/services/app_sign_up_service_spec.rb | 41 ++++++++++++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 spec/services/app_sign_up_service_spec.rb (limited to 'spec') diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index c506fb5f0..f5f65c000 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -19,6 +19,40 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end end + describe 'POST #create' do + let(:app) { Fabricate(:application) } + let(:token) { Doorkeeper::AccessToken.find_or_create_for(app, nil, 'read write', nil, false) } + let(:agreement) { nil } + + before do + post :create, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement } + end + + context 'given truthy agreement' do + let(:agreement) { 'true' } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns a new access token as JSON' do + expect(body_as_json[:access_token]).to_not be_blank + end + + it 'creates a user' do + user = User.find_by(email: 'hello@world.tld') + expect(user).to_not be_nil + expect(user.created_by_application_id).to eq app.id + end + end + + context 'given no agreement' do + it 'returns http unprocessable entity' do + expect(response).to have_http_status(422) + end + end + end + describe 'GET #show' do let(:scopes) { 'read:accounts' } diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb index 7dfbdb52d..8f5956501 100644 --- a/spec/fabricators/user_fabricator.rb +++ b/spec/fabricators/user_fabricator.rb @@ -3,4 +3,5 @@ Fabricator(:user) do email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } } password "123456789" confirmed_at { Time.zone.now } + agreement true end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c82919597..856254ce4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -106,19 +106,19 @@ RSpec.describe User, type: :model do end it 'should allow a non-blacklisted user to be created' do - user = User.new(email: 'foo@example.com', account: account, password: password) + user = User.new(email: 'foo@example.com', account: account, password: password, agreement: true) expect(user.valid?).to be_truthy end it 'should not allow a blacklisted user to be created' do - user = User.new(email: 'foo@mvrht.com', account: account, password: password) + user = User.new(email: 'foo@mvrht.com', account: account, password: password, agreement: true) expect(user.valid?).to be_falsey end it 'should not allow a subdomain blacklisted user to be created' do - user = User.new(email: 'foo@mvrht.com.topdomain.tld', account: account, password: password) + user = User.new(email: 'foo@mvrht.com.topdomain.tld', account: account, password: password, agreement: true) expect(user.valid?).to be_falsey end @@ -210,17 +210,17 @@ RSpec.describe User, type: :model do end it 'should not allow a user to be created unless they are whitelisted' do - user = User.new(email: 'foo@example.com', account: account, password: password) + user = User.new(email: 'foo@example.com', account: account, password: password, agreement: true) expect(user.valid?).to be_falsey end it 'should allow a user to be created if they are whitelisted' do - user = User.new(email: 'foo@mastodon.space', account: account, password: password) + user = User.new(email: 'foo@mastodon.space', account: account, password: password, agreement: true) expect(user.valid?).to be_truthy end it 'should not allow a user with a whitelisted top domain as subdomain in their email address to be created' do - user = User.new(email: 'foo@mastodon.space.userdomain.com', account: account, password: password) + user = User.new(email: 'foo@mastodon.space.userdomain.com', account: account, password: password, agreement: true) expect(user.valid?).to be_falsey end @@ -242,7 +242,7 @@ RSpec.describe User, type: :model do it_behaves_like 'Settings-extended' do def create! - User.create!(account: Fabricate(:account), email: 'foo@mastodon.space', password: 'abcd1234') + User.create!(account: Fabricate(:account), email: 'foo@mastodon.space', password: 'abcd1234', agreement: true) end def fabricate diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb new file mode 100644 index 000000000..d480df348 --- /dev/null +++ b/spec/services/app_sign_up_service_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe AppSignUpService, type: :service do + let(:app) { Fabricate(:application, scopes: 'read write') } + let(:good_params) { { username: 'alice', password: '12345678', email: 'good@email.com', agreement: true } } + + subject { described_class.new } + + describe '#call' do + it 'returns nil when registrations are closed' do + Setting.open_registrations = false + expect(subject.call(app, good_params)).to be_nil + end + + it 'raises an error when params are missing' do + expect { subject.call(app, {}) }.to raise_error ActiveRecord::RecordInvalid + end + + it 'creates an unconfirmed user with access token' do + access_token = subject.call(app, good_params) + expect(access_token).to_not be_nil + user = User.find_by(id: access_token.resource_owner_id) + expect(user).to_not be_nil + expect(user.confirmed?).to be false + end + + it 'creates access token with the app\'s scopes' do + access_token = subject.call(app, good_params) + expect(access_token).to_not be_nil + expect(access_token.scopes.to_s).to eq 'read write' + end + + it 'creates an account' do + access_token = subject.call(app, good_params) + expect(access_token).to_not be_nil + user = User.find_by(id: access_token.resource_owner_id) + expect(user).to_not be_nil + expect(user.account).to_not be_nil + end + end +end -- cgit