Skip to content

Commit 0526c13

Browse files
[TBT-381] Create beta_plan for org
1 parent e461de2 commit 0526c13

File tree

3 files changed

+96
-27
lines changed

3 files changed

+96
-27
lines changed

lib/travis/api/app/endpoint/assembla.rb

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,42 @@
22
require 'jwt'
33
require 'travis/remote_vcs/user'
44
require 'travis/remote_vcs/repository'
5+
require 'travis/api/v3/billing_client'
6+
require_relative '../jwt_utils'
57

68
class Travis::Api::App
79
class Endpoint
810
class Assembla < Endpoint
11+
include Travis::Api::App::JWTUtils
912
set prefix: '/assembla'
1013
set :check_auth, false
1114

1215
before do
1316
halt 403, { error: 'Deep integration not enabled' } unless deep_integration_enabled?
1417
halt 403, { error: 'Invalid ASM cluster' } unless valid_asm_cluster?
15-
@jwt_payload = verify_jwt
18+
begin
19+
@jwt_payload = verify_jwt(request, Travis.config.assembla_jwt_secret)
20+
rescue JWTUtils::UnauthorizedError => e
21+
halt 401, { error: e.message }.to_json
22+
end
1623
end
1724

1825
# POST /assembla/login
1926
# Accepts a JWT, finds or creates a user, and signs them in
2027
post '/login' do
2128
user = find_or_create_user(@jwt_payload)
22-
begin
23-
Travis::RemoteVCS::User.new.sync(user_id: user.id)
24-
rescue => e
25-
halt 500, { error: 'User sync failed', details: e.message }.to_json
26-
end
27-
{ user_id: user.id, login: user.login, token: user.token, status: 'signed_in' }.to_json
28-
end
29-
30-
private
29+
sync_user(user.id)
30+
create_org_subscription(user.id, @jwt_payload[:space_id])
3131

32-
def verify_jwt
33-
token = extract_jwt_token
34-
halt 401, { error: 'Missing JWT' } unless token
35-
secret = Travis.config.assembla_jwt_secret
36-
begin
37-
decoded, = JWT.decode(token, secret, true, { algorithm: 'HS256' })
38-
decoded
39-
rescue JWT::DecodeError => e
40-
halt 401, { error: 'Invalid JWT', details: e.message }
41-
end
32+
{
33+
user_id: user.id,
34+
login: user.login,
35+
token: user.token,
36+
status: 'signed_in'
37+
}.to_json
4238
end
4339

44-
def extract_jwt_token
45-
request.env['HTTP_AUTHORIZATION']&.split(' ')&.last
46-
end
40+
private
4741

4842
def deep_integration_enabled?
4943
Travis.config.deep_integration_enabled
@@ -69,7 +63,24 @@ def find_or_create_user(payload)
6963
org_id: payload['space_id'],
7064
vcs_type: 'AssemblaUser'
7165
}
72-
::User.first_or_create!(attrs)
66+
::User.find_or_create_by!(attrs)
67+
end
68+
69+
def sync_user(user_id)
70+
Travis::RemoteVCS::User.new.sync(user_id: user_id)
71+
rescue => e
72+
halt 500, { error: 'User sync failed', details: e.message }.to_json
73+
end
74+
75+
def create_org_subscription(user_id, space_id)
76+
plan = 'beta_plan'
77+
client = Travis::API::V3::BillingClient.new(user_id)
78+
client.create_v2_subscription({
79+
'plan' => plan,
80+
'organization_id' => space_id,
81+
})
82+
rescue => e
83+
halt 500, { error: 'Subscription creation failed', details: e.message }.to_json
7384
end
7485
end
7586
end

lib/travis/api/app/jwt_utils.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class Travis::Api::App
2+
module JWTUtils
3+
def extract_jwt_token(request)
4+
request.env['HTTP_AUTHORIZATION']&.split(' ')&.last
5+
end
6+
7+
def verify_jwt(request, secret)
8+
token = extract_jwt_token(request)
9+
raise UnauthorizedError, 'Missing JWT' unless token
10+
begin
11+
decoded, = JWT.decode(token, secret, true, { algorithm: 'HS256' })
12+
decoded
13+
rescue JWT::DecodeError => e
14+
raise UnauthorizedError, "Invalid JWT: #{e.message}"
15+
end
16+
end
17+
18+
class UnauthorizedError < StandardError; end
19+
end
20+
end

spec/unit/endpoint/assembla_spec.rb

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,17 @@
2727
describe 'POST /assembla/login' do
2828
context 'with valid JWT' do
2929
before do
30-
allow(::User).to receive(:first_or_create!).and_return(double('User', id: 1, login: 'testuser', token: 'abc123'))
3130
allow_any_instance_of(Travis::RemoteVCS::User).to receive(:sync).and_return(true)
31+
allow_any_instance_of(Travis::API::V3::BillingClient).to receive(:create_v2_subscription).and_return(true)
3232
end
3333

3434
it 'returns user info and token' do
3535
header 'Authorization', "Bearer #{token}"
3636
post '/assembla/login'
3737
expect(last_response.status).to eq(200)
3838
body = JSON.parse(last_response.body)
39-
expect(body['user_id']).to eq(1)
4039
expect(body['login']).to eq('testuser')
41-
expect(body['token']).to eq('abc123')
40+
expect(body['token']).to be_present
4241
expect(body['status']).to eq('signed_in')
4342
end
4443
end
@@ -94,5 +93,44 @@
9493
expect(last_response.body).to include('Invalid ASM cluster')
9594
end
9695
end
96+
97+
context 'with missing required fields in JWT payload' do
98+
let(:payload) do
99+
{
100+
'name' => 'Test User',
101+
'login' => 'testuser',
102+
'space_id' => 'space123' # 'email' is missing
103+
}
104+
end
105+
let(:token) { JWT.encode(payload, jwt_secret, 'HS256') }
106+
107+
it 'returns 400 with missing fields' do
108+
header 'Authorization', "Bearer #{token}"
109+
post '/assembla/login'
110+
expect(last_response.status).to eq(400)
111+
expect(last_response.body).to include('Missing required fields')
112+
expect(last_response.body).to include('email')
113+
end
114+
end
115+
116+
context 'with expired JWT token' do
117+
let(:payload) do
118+
{
119+
'name' => 'Test User',
120+
'email' => '[email protected]',
121+
'login' => 'testuser',
122+
'space_id' => 'space123',
123+
'exp' => (Time.now.to_i - 60)
124+
}
125+
end
126+
let(:token) { JWT.encode(payload, jwt_secret, 'HS256') }
127+
128+
it 'returns 401 with expired error' do
129+
header 'Authorization', "Bearer #{token}"
130+
post '/assembla/login'
131+
expect(last_response.status).to eq(401)
132+
expect(last_response.body).to match(/expired|exp/i)
133+
end
134+
end
97135
end
98136
end

0 commit comments

Comments
 (0)