Commit 2a18538

mo <mo.khan@gmail.com>
2018-09-17 00:56:20
add resource owner password grant.
1 parent 2f80a03
Changed files (4)
app/controllers/oauths_controller.rb
@@ -30,6 +30,10 @@ class OauthsController < ApplicationController
       @access_token, @refresh_token = Token.find_by!(uuid: jti).exchange
     elsif token_params[:grant_type] == 'client_credentials'
       @access_token = current_client.exchange
+    elsif token_params[:grant_type] == 'password'
+      user = User.login(params[:username], params[:password])
+      return render "bad_request", formats: :json, status: :bad_request unless user
+      @access_token, @refresh_token = user.issue_tokens_to(current_client)
     else
       return render "bad_request", formats: :json, status: :bad_request
     end
app/models/user.rb
@@ -18,6 +18,15 @@ class User < ApplicationRecord
     request.trusted? ? trusted_attributes_for(request) : {}
   end
 
+  def issue_tokens_to(client)
+    transaction do
+      [
+        Token.create!(subject: self, audience: client, token_type: :access),
+        Token.create!(subject: self, audience: client, token_type: :refresh)
+      ]
+    end
+  end
+
   def mfa
     Mfa.new(self)
   end
@@ -46,10 +55,6 @@ class User < ApplicationRecord
   private
 
   def trusted_attributes_for(_request)
-    {
-      id: uuid,
-      email: email,
-      created_at: created_at,
-    }
+    { id: uuid, email: email, created_at: created_at }
   end
 end
spec/requests/oauth_spec.rb
@@ -129,6 +129,32 @@ RSpec.describe '/oauth' do
       end
     end
 
+    context "when requesting tokens using the resource owner password credentials grant" do
+      context "when the credentials are valid" do
+        let(:user) { create(:user) }
+        before { post '/oauth/token', params: { grant_type: 'password', username: user.email, password: user.password }, headers: headers }
+
+        specify { expect(response).to have_http_status(:ok) }
+        specify { expect(response.headers['Content-Type']).to include('application/json') }
+        specify { expect(response.headers['Cache-Control']).to include('no-store') }
+        specify { expect(response.headers['Pragma']).to eql('no-cache') }
+
+        let(:json) { JSON.parse(response.body, symbolize_names: true) }
+        specify { expect(json[:access_token]).to be_present }
+        specify { expect(json[:token_type]).to eql('Bearer') }
+        specify { expect(json[:expires_in]).to eql(1.hour.to_i) }
+        specify { expect(json[:refresh_token]).to be_present }
+      end
+
+      context "when the credentials are invalid" do
+        before { post '/oauth/token', params: { grant_type: 'password', username: generate(:email), password: generate(:password) }, headers: headers }
+
+        specify { expect(response).to have_http_status(:bad_request) }
+        let(:json) { JSON.parse(response.body, symbolize_names: true) }
+        specify { expect(json[:error]).to eql('invalid_request') }
+      end
+    end
+
     context "when exchanging a refresh token for a new access token" do
       context "when the refresh token is still active" do
         let(:refresh_token) { create(:refresh_token) }
spec/factories.rb
@@ -1,4 +1,7 @@
 FactoryBot.define do
+  sequence(:email) { |n| FFaker::Internet.email }
+  sequence(:password) { |n| FFaker::Internet.password }
+
   factory :token do
     uuid { SecureRandom.uuid }
     authorization { nil }