Commit 2a18538
Changed files (4)
app
controllers
models
spec
requests
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 }