about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-08-26 19:12:19 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-08-26 19:12:19 +0200
commit92afd296509de82e7550f67064b032db916b1f63 (patch)
tree6d723210f723d0a74317805352e7912b70c54240
parent44e57f64dd8b00900c31d7fd56fda94f4e69e986 (diff)
The frontend will now be an OAuth app, auto-authorized. The frontend will use an access token for API requests
Adding better errors for the API controllers, posting a simple status works from the frontend now
-rw-r--r--Dockerfile4
-rw-r--r--app/assets/javascripts/components/actions/meta.jsx8
-rw-r--r--app/assets/javascripts/components/actions/statuses.jsx56
-rw-r--r--app/assets/javascripts/components/components/composer_drawer.jsx1
-rw-r--r--app/assets/javascripts/components/containers/root.jsx11
-rw-r--r--app/assets/javascripts/components/reducers/index.jsx4
-rw-r--r--app/assets/javascripts/components/reducers/meta.jsx13
-rw-r--r--app/controllers/api_controller.rb8
-rw-r--r--app/controllers/home_controller.rb7
-rw-r--r--app/views/home/index.html.haml2
-rw-r--r--config/environments/development.rb2
-rw-r--r--config/initializers/doorkeeper.rb8
-rw-r--r--db/migrate/20160826155805_add_superapp_to_oauth_applications.rb5
-rw-r--r--db/schema.rb13
-rw-r--r--db/seeds.rb9
-rw-r--r--package.json1
16 files changed, 127 insertions, 25 deletions
diff --git a/Dockerfile b/Dockerfile
index 2b15d437b..3acef6e13 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,9 +9,9 @@ WORKDIR /mastodon
 
 ADD Gemfile /mastodon/Gemfile
 ADD Gemfile.lock /mastodon/Gemfile.lock
-ADD package.json /mastodon/package.json
-
 RUN bundle install --deployment --without test development
+
+ADD package.json /mastodon/package.json
 RUN npm install
 
 ADD . /mastodon
diff --git a/app/assets/javascripts/components/actions/meta.jsx b/app/assets/javascripts/components/actions/meta.jsx
new file mode 100644
index 000000000..1b64db1c2
--- /dev/null
+++ b/app/assets/javascripts/components/actions/meta.jsx
@@ -0,0 +1,8 @@
+export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN';
+
+export function setAccessToken(token) {
+  return {
+    type: SET_ACCESS_TOKEN,
+    token: token
+  };
+}
diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx
index fece257d5..45d62fab2 100644
--- a/app/assets/javascripts/components/actions/statuses.jsx
+++ b/app/assets/javascripts/components/actions/statuses.jsx
@@ -2,7 +2,11 @@ import fetch from 'isomorphic-fetch'
 
 export const SET_TIMELINE = 'SET_TIMELINE';
 export const ADD_STATUS   = 'ADD_STATUS';
-export const PUBLISH      = 'PUBLISH';
+
+export const PUBLISH       = 'PUBLISH';
+export const PUBLISH_START = 'PUBLISH_START';
+export const PUBLISH_SUCC  = 'PUBLISH_SUCC';
+export const PUBLISH_ERROR = 'PUBLISH_ERROR';
 
 export function setTimeline(timeline, statuses) {
   return {
@@ -20,14 +24,58 @@ export function addStatus(timeline, status) {
   };
 }
 
+export function publishStart() {
+  return {
+    type: PUBLISH_START
+  };
+}
+
+export function publishError(error) {
+  return {
+    type: PUBLISH_ERROR,
+    error: error
+  };
+}
+
+export function publishSucc(status) {
+  return {
+    type: PUBLISH_SUCC,
+    status: status
+  };
+}
+
 export function publish(text, in_reply_to_id) {
-  return function (dispatch) {
+  return function (dispatch, getState) {
+    const access_token = getState().getIn(['meta', 'access_token']);
+
+    var data = new FormData();
+
+    data.append('status', text);
+
+    if (in_reply_to_id !== null) {
+      data.append('in_reply_to_id', in_reply_to_id);
+    }
+
+    dispatch(publishStart());
+
     return fetch('/api/statuses', {
-      method: 'POST'
+      method: 'POST',
+
+      headers: {
+        'Authorization': `Bearer ${access_token}`
+      },
+
+      body: data
     }).then(function (response) {
       return response.json();
     }).then(function (json) {
-      console.log(json);
+      if (json.error) {
+        dispatch(publishError(json.error));
+      } else {
+        dispatch(publishSucc(json));
+      }
+    }).catch(function (error) {
+      dispatch(publishError(error));
     });
   };
 }
diff --git a/app/assets/javascripts/components/components/composer_drawer.jsx b/app/assets/javascripts/components/components/composer_drawer.jsx
index d33e28219..7b742f64e 100644
--- a/app/assets/javascripts/components/components/composer_drawer.jsx
+++ b/app/assets/javascripts/components/components/composer_drawer.jsx
@@ -26,6 +26,7 @@ const ComposerDrawer = React.createClass({
 
   handleSubmit () {
     this.props.onSubmit(this.state.text, null);
+    this.setState({ text: '' });
   },
 
   render () {
diff --git a/app/assets/javascripts/components/containers/root.jsx b/app/assets/javascripts/components/containers/root.jsx
index 7da984d89..661ffb22c 100644
--- a/app/assets/javascripts/components/containers/root.jsx
+++ b/app/assets/javascripts/components/containers/root.jsx
@@ -2,12 +2,23 @@ import { Provider }               from 'react-redux';
 import configureStore             from '../store/configureStore';
 import Frontend                   from '../components/frontend';
 import { setTimeline, addStatus } from '../actions/statuses';
+import { setAccessToken }         from '../actions/meta';
+import PureRenderMixin            from 'react-addons-pure-render-mixin';
 
 const store = configureStore();
 
 const Root = React.createClass({
 
+  propTypes: {
+    token: React.PropTypes.string.isRequired,
+    timelines: React.PropTypes.array
+  },
+
+  mixins: [PureRenderMixin],
+
   componentWillMount() {
+    store.dispatch(setAccessToken(this.props.token));
+
     for (var timelineType in this.props.timelines) {
       if (this.props.timelines.hasOwnProperty(timelineType)) {
         store.dispatch(setTimeline(timelineType, JSON.parse(this.props.timelines[timelineType])));
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx
index c7e858f38..96c026c8c 100644
--- a/app/assets/javascripts/components/reducers/index.jsx
+++ b/app/assets/javascripts/components/reducers/index.jsx
@@ -1,6 +1,8 @@
 import { combineReducers } from 'redux-immutable';
 import statuses            from './statuses';
+import meta                from './meta';
 
 export default combineReducers({
-  statuses
+  statuses,
+  meta
 });
diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx
new file mode 100644
index 000000000..401be435d
--- /dev/null
+++ b/app/assets/javascripts/components/reducers/meta.jsx
@@ -0,0 +1,13 @@
+import { SET_ACCESS_TOKEN }         from '../actions/meta';
+import Immutable                    from 'immutable';
+
+const initialState = Immutable.Map();
+
+export default function meta(state = initialState, action) {
+  switch(action.type) {
+    case SET_ACCESS_TOKEN:
+      return state.set('access_token', action.token);
+    default:
+      return state;
+  }
+}
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index 8a2712476..bacdd997b 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -2,6 +2,14 @@ class ApiController < ApplicationController
   protect_from_forgery with: :null_session
   skip_before_action :verify_authenticity_token
 
+  rescue_from ActiveRecord::RecordInvalid do
+    render json: { error: 'Record invalid' }, status: 422
+  end
+
+  rescue_from ActiveRecord::RecordNotFound do
+    render json: { error: 'Record not found' }, status: 404
+  end
+
   protected
 
   def current_resource_owner
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 57973ba49..f159c3df8 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -5,5 +5,12 @@ class HomeController < ApplicationController
     @body_classes = 'app-body'
     @home         = Feed.new(:home, current_user.account).get(20)
     @mentions     = Feed.new(:mentions, current_user.account).get(20)
+    @token        = find_or_create_access_token.token
+  end
+
+  private
+
+  def find_or_create_access_token
+    Doorkeeper::AccessToken.find_or_create_for(Doorkeeper::Application.where(superapp: true).first, current_user.id, nil, Doorkeeper.configuration.access_token_expires_in, Doorkeeper.configuration.refresh_token_enabled?)
   end
 end
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 941397384..9279ae9ae 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1 +1 @@
-= react_component 'Root', { timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false
+= react_component 'Root', { token: @token, timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false
diff --git a/config/environments/development.rb b/config/environments/development.rb
index c51d98543..288256dcf 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -62,6 +62,8 @@ Rails.application.configure do
     Bullet.enable = true
     Bullet.bullet_logger = true
     Bullet.rails_logger = true
+
+    Bullet.add_whitelist type: :n_plus_one_query, class_name: 'User', association: :account
   end
 
   config.react.variant = :development
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index cf320c557..0d6574d9f 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -4,7 +4,7 @@ Doorkeeper.configure do
 
   # This block will be called to check whether the resource owner is authenticated or not.
   resource_owner_authenticator do
-    current_user || warden.authenticate!(scope: :user)
+    current_user || redirect_to(new_user_session_url)
   end
 
   resource_owner_from_credentials do |routes|
@@ -100,9 +100,9 @@ Doorkeeper.configure do
   # Under some circumstances you might want to have applications auto-approved,
   # so that the user skips the authorization step.
   # For example if dealing with a trusted application.
-  # skip_authorization do |resource_owner, client|
-  #   client.superapp? or resource_owner.admin?
-  # end
+  skip_authorization do |resource_owner, client|
+    client.superapp?
+  end
 
   # WWW-Authenticate Realm (default "Doorkeeper").
   # realm "Doorkeeper"
diff --git a/db/migrate/20160826155805_add_superapp_to_oauth_applications.rb b/db/migrate/20160826155805_add_superapp_to_oauth_applications.rb
new file mode 100644
index 000000000..a1b92fada
--- /dev/null
+++ b/db/migrate/20160826155805_add_superapp_to_oauth_applications.rb
@@ -0,0 +1,5 @@
+class AddSuperappToOauthApplications < ActiveRecord::Migration[5.0]
+  def change
+    add_column :oauth_applications, :superapp, :boolean, default: false, null: false
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 45ab70855..b63df0fea 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160325130944) do
+ActiveRecord::Schema.define(version: 20160826155805) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -94,15 +94,16 @@ ActiveRecord::Schema.define(version: 20160325130944) do
   end
 
   create_table "oauth_applications", force: :cascade do |t|
-    t.string   "name",                      null: false
-    t.string   "uid",                       null: false
-    t.string   "secret",                    null: false
-    t.text     "redirect_uri",              null: false
-    t.string   "scopes",       default: "", null: false
+    t.string   "name",                         null: false
+    t.string   "uid",                          null: false
+    t.string   "secret",                       null: false
+    t.text     "redirect_uri",                 null: false
+    t.string   "scopes",       default: "",    null: false
     t.datetime "created_at"
     t.datetime "updated_at"
     t.integer  "owner_id"
     t.string   "owner_type"
+    t.boolean  "superapp",     default: false, null: false
     t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
     t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
   end
diff --git a/db/seeds.rb b/db/seeds.rb
index 4edb1e857..c2bf6a16e 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1,7 +1,2 @@
-# This file should contain all the record creation needed to seed the database with its default values.
-# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
-#
-# Examples:
-#
-#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
-#   Mayor.create(name: 'Emanuel', city: cities.first)
+web_app = Doorkeeper::Application.new(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri)
+web_app.save(validate: false)
diff --git a/package.json b/package.json
index 19f0ac016..4b5a615fa 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
     "immutable": "^3.8.1",
     "isomorphic-fetch": "^2.2.1",
     "moment": "^2.14.1",
+    "react-addons-pure-render-mixin": "^15.3.1",
     "react-immutable-proptypes": "^2.1.0",
     "react-redux": "^4.4.5",
     "redux": "^3.5.2",