1+ use base64:: { engine:: general_purpose:: URL_SAFE_NO_PAD , Engine } ;
12use chrono:: { DateTime , TimeZone , Utc } ;
23use serde:: { Deserialize , Serialize } ;
34use std:: fs;
@@ -9,6 +10,20 @@ use crate::error::Error;
910use tower_api:: apis:: default_api:: describe_session;
1011use tower_telemetry:: debug;
1112
13+ /// Extracts the account ID (aid) from a Tower JWT token.
14+ /// Returns None if the JWT is malformed or doesn't contain an aid.
15+ fn extract_aid_from_jwt ( jwt : & str ) -> Option < String > {
16+ let parts: Vec < & str > = jwt. split ( '.' ) . collect ( ) ;
17+ if parts. len ( ) != 3 {
18+ return None ;
19+ }
20+
21+ let payload = parts[ 1 ] ;
22+ let decoded = URL_SAFE_NO_PAD . decode ( payload) . ok ( ) ?;
23+ let json: serde_json:: Value = serde_json:: from_slice ( & decoded) . ok ( ) ?;
24+ json. get ( "https://tower.dev/aid" ) ?. as_str ( ) . map ( String :: from)
25+ }
26+
1227const DEFAULT_TOWER_URL : & str = "https://api.tower.dev" ;
1328
1429pub fn default_tower_url ( ) -> Url {
@@ -163,6 +178,22 @@ impl Session {
163178 }
164179 }
165180
181+ /// Sets the active team based on an account ID (aid) extracted from a JWT.
182+ /// Returns true if a matching team was found and set as active, false otherwise.
183+ pub fn set_active_team_by_aid ( & mut self , aid : & str ) -> bool {
184+ // Find the team whose JWT contains the matching aid
185+ if let Some ( team) = self
186+ . teams
187+ . iter ( )
188+ . find ( |team| extract_aid_from_jwt ( & team. token . jwt ) . as_deref ( ) == Some ( aid) )
189+ {
190+ self . active_team = Some ( team. clone ( ) ) ;
191+ true
192+ } else {
193+ false
194+ }
195+ }
196+
166197 /// Updates the session with data from the API response
167198 pub fn update_from_api_response (
168199 & mut self ,
@@ -263,36 +294,30 @@ impl Session {
263294 }
264295
265296 pub fn from_jwt ( jwt : & str ) -> Result < Self , Error > {
266- // We need to instantiate our own configuration object here, instead of the typical thing
267- // that we do which is turn a Config into a Configuration.
297+ let jwt_aid = extract_aid_from_jwt ( jwt ) ;
298+
268299 let mut config = tower_api:: apis:: configuration:: Configuration :: new ( ) ;
269300 config. bearer_access_token = Some ( jwt. to_string ( ) ) ;
270301
271- // We only pull TOWER_URL out of the environment here because we only ever use the JWT and
272- // all that in programmatic contexts (when TOWER_URL is set).
273- let tower_url = if let Ok ( val) = std:: env:: var ( "TOWER_URL" ) {
274- val
275- } else {
276- DEFAULT_TOWER_URL . to_string ( )
277- } ;
278-
279- // Setup the base path to point to the /v1 API endpoint as expected.
302+ let tower_url = std:: env:: var ( "TOWER_URL" ) . unwrap_or ( DEFAULT_TOWER_URL . to_string ( ) ) ;
280303 let mut base_path = Url :: parse ( & tower_url) . unwrap ( ) ;
281304 base_path. set_path ( "/v1" ) ;
282-
283305 config. base_path = base_path. to_string ( ) ;
284306
285- // This is a bit of a hairy thing: I didn't want to pull in too much from the Tower API
286- // client, so we're using the raw bindings here.
287307 match run_future_sync ( describe_session ( & config) ) {
288308 Ok ( resp) => {
289- // Now we need to extract the session from the response.
290309 let entity = resp. entity . unwrap ( ) ;
291310
292311 match entity {
293312 tower_api:: apis:: default_api:: DescribeSessionSuccess :: Status200 ( resp) => {
294313 let mut session = Session :: from_api_session ( & resp. session ) ;
295314 session. tower_url = base_path;
315+
316+ if let Some ( aid) = jwt_aid {
317+ session. set_active_team_by_aid ( & aid) ;
318+ }
319+
320+ session. save ( ) ?;
296321 Ok ( session)
297322 }
298323 tower_api:: apis:: default_api:: DescribeSessionSuccess :: UnknownValue ( val) => {
0 commit comments