feat: Ability to access user tokens via Platform API (#11537)

- Add Platform API for generating user tokens 
- Add the swagger documentation.

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose
2025-05-21 23:00:04 -07:00
committed by GitHub
parent 8565341682
commit 8697a30dc5
7 changed files with 182 additions and 3 deletions

View File

@@ -1,9 +1,9 @@
class Platform::Api::V1::UsersController < PlatformController
# ref: https://stackoverflow.com/a/45190318/939299
# set resource is called for other actions already in platform controller
# we want to add login to that chain as well
before_action(only: [:login]) { set_resource }
before_action(only: [:login]) { validate_platform_app_permissible }
# we want to add login and token to that chain as well
before_action(only: [:login, :token]) { set_resource }
before_action(only: [:login, :token]) { validate_platform_app_permissible }
def show; end
@@ -18,6 +18,8 @@ class Platform::Api::V1::UsersController < PlatformController
render json: { url: @resource.generate_sso_link }
end
def token; end
def update
@resource.assign_attributes(user_update_params)

View File

@@ -0,0 +1,9 @@
json.access_token @resource.access_token.token
json.expiry nil
json.user do
json.id @resource.id
json.name @resource.name
json.display_name @resource.display_name
json.email @resource.email
json.pubsub_token @resource.pubsub_token
end

View File

@@ -396,6 +396,7 @@ Rails.application.routes.draw do
resources :users, only: [:create, :show, :update, :destroy] do
member do
get :login
post :token
end
end
resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do

View File

@@ -76,6 +76,54 @@ RSpec.describe 'Platform Users API', type: :request do
end
end
describe 'POST /platform/api/v1/users/{user_id}/token' do
context 'when it is an unauthenticated platform app' do
it 'returns unauthorized' do
post "/platform/api/v1/users/#{user.id}/token"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an invalid platform app token' do
it 'returns unauthorized' do
post "/platform/api/v1/users/#{user.id}/token", headers: { api_access_token: 'invalid' }, as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated platform app' do
let(:platform_app) { create(:platform_app) }
it 'returns unauthorized when its not a permissible object' do
post "/platform/api/v1/users/#{user.id}/token", headers: { api_access_token: platform_app.access_token.token }, as: :json
expect(response).to have_http_status(:unauthorized)
end
it 'returns access token for the user with expiry and user info' do
create(:platform_app_permissible, platform_app: platform_app, permissible: user)
post "/platform/api/v1/users/#{user.id}/token",
headers: { api_access_token: platform_app.access_token.token }, as: :json
expect(response).to have_http_status(:success)
data = response.parsed_body
# Check access token and expiry
expect(data['access_token']).to eq(user.access_token.token)
expect(data['expiry']).to be_nil
# Check user info
expect(data['user']).to include(
'id' => user.id,
'name' => user.name,
'display_name' => user.display_name,
'email' => user.email,
'pubsub_token' => user.pubsub_token
)
end
end
end
describe 'POST /platform/api/v1/users/' do
context 'when it is an unauthenticated platform app' do
it 'returns unauthorized' do

View File

@@ -64,6 +64,11 @@
- $ref: '#/parameters/platform_user_id'
get:
$ref: './platform/users/login.yml'
/platform/api/v1/users/{id}/token:
parameters:
- $ref: '#/parameters/platform_user_id'
post:
$ref: './platform/users/token.yml'
# ---------------- end of platform path -----------#

View File

@@ -0,0 +1,42 @@
tags:
- Users
operationId: post-user-token
summary: Get User Access Token
description: Get the access token of a user
security:
- platformAppApiKey: []
responses:
200:
description: Success
schema:
type: object
properties:
access_token:
type: string
description: Access token of the user
expiry:
type: [integer, "null"]
description: Expiry timestamp
user:
type: object
properties:
id:
type: integer
description: User ID
name:
type: string
description: User's full name
display_name:
type: string
description: User's display name
email:
type: string
description: User's email address
pubsub_token:
type: string
description: User's pubsub token
401:
description: Unauthorized
404:
description: The given user does not exist

View File

@@ -655,6 +655,78 @@
}
}
},
"/platform/api/v1/users/{id}/token": {
"parameters": [
{
"$ref": "#/parameters/platform_user_id"
}
],
"post": {
"tags": [
"Users"
],
"operationId": "post-user-token",
"summary": "Get User Access Token",
"description": "Get the access token of a user",
"security": [
{
"platformAppApiKey": []
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"type": "object",
"properties": {
"access_token": {
"type": "string",
"description": "Access token of the user"
},
"expiry": {
"type": [
"integer",
"null"
],
"description": "Expiry timestamp"
},
"user": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "User ID"
},
"name": {
"type": "string",
"description": "User's full name"
},
"display_name": {
"type": "string",
"description": "User's display name"
},
"email": {
"type": "string",
"description": "User's email address"
},
"pubsub_token": {
"type": "string",
"description": "User's pubsub token"
}
}
}
}
}
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "The given user does not exist"
}
}
}
},
"/public/api/v1/inboxes/{inbox_identifier}": {
"parameters": [
{