@@ -16,8 +16,9 @@ use axum_extra::TypedHeader;
1616use headers:: { Authorization , authorization:: Bearer } ;
1717use hyper:: StatusCode ;
1818use mas_axum_utils:: record_error;
19- use mas_data_model:: { BoxClock , Session , User } ;
19+ use mas_data_model:: { BoxClock , Session , TokenType , User , personal :: session :: PersonalSession } ;
2020use mas_storage:: { BoxRepository , RepositoryError } ;
21+ use oauth2_types:: scope:: Scope ;
2122use ulid:: Ulid ;
2223
2324use super :: response:: ErrorResponse ;
@@ -41,6 +42,10 @@ pub enum Rejection {
4142 #[ error( "Invalid repository operation" ) ]
4243 Repository ( #[ from] RepositoryError ) ,
4344
45+ /// The access token was not of the correct type for the Admin API
46+ #[ error( "Invalid type of access token" ) ]
47+ InvalidAccessTokenType ,
48+
4449 /// The access token could not be found in the database
4550 #[ error( "Unknown access token" ) ]
4651 UnknownAccessToken ,
@@ -90,7 +95,8 @@ impl IntoResponse for Rejection {
9095 | Rejection :: TokenExpired
9196 | Rejection :: SessionRevoked
9297 | Rejection :: UserLocked
93- | Rejection :: MissingScope => StatusCode :: UNAUTHORIZED ,
98+ | Rejection :: MissingScope
99+ | Rejection :: InvalidAccessTokenType => StatusCode :: UNAUTHORIZED ,
94100
95101 Rejection :: RepositorySetup ( _)
96102 | Rejection :: Repository ( _)
@@ -113,7 +119,7 @@ pub struct CallContext {
113119 pub repo : BoxRepository ,
114120 pub clock : BoxClock ,
115121 pub user : Option < User > ,
116- pub session : Session ,
122+ pub session : CallerSession ,
117123}
118124
119125impl < S > FromRequestParts < S > for CallContext
@@ -154,28 +160,76 @@ where
154160 } ) ?;
155161
156162 let token = token. token ( ) ;
163+ let token_type = TokenType :: check ( token) . or ( Err ( Rejection :: InvalidAccessTokenType ) ) ?;
164+
165+ let session = match token_type {
166+ TokenType :: AccessToken => {
167+ // Look for the access token in the database
168+ let token = repo
169+ . oauth2_access_token ( )
170+ . find_by_token ( token)
171+ . await ?
172+ . ok_or ( Rejection :: UnknownAccessToken ) ?;
173+
174+ // Look for the associated session in the database
175+ let session = repo
176+ . oauth2_session ( )
177+ . lookup ( token. session_id )
178+ . await ?
179+ . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
180+
181+ if !session. is_valid ( ) {
182+ return Err ( Rejection :: SessionRevoked ) ;
183+ }
184+
185+ if !token. is_valid ( clock. now ( ) ) {
186+ return Err ( Rejection :: TokenExpired ) ;
187+ }
188+
189+ // Record the activity on the session
190+ activity_tracker
191+ . record_oauth2_session ( & clock, & session)
192+ . await ;
193+
194+ CallerSession :: OAuth2Session ( session)
195+ }
196+ TokenType :: PersonalAccessToken => {
197+ // Look for the access token in the database
198+ let token = repo
199+ . personal_access_token ( )
200+ . find_by_token ( token)
201+ . await ?
202+ . ok_or ( Rejection :: UnknownAccessToken ) ?;
203+
204+ // Look for the associated session in the database
205+ let session = repo
206+ . personal_session ( )
207+ . lookup ( token. session_id )
208+ . await ?
209+ . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
210+
211+ if !session. is_valid ( ) {
212+ return Err ( Rejection :: SessionRevoked ) ;
213+ }
214+
215+ if !token. is_valid ( clock. now ( ) ) {
216+ return Err ( Rejection :: TokenExpired ) ;
217+ }
218+
219+ // Record the activity on the session
220+ activity_tracker
221+ . record_personal_session ( & clock, & session)
222+ . await ;
157223
158- // Look for the access token in the database
159- let token = repo
160- . oauth2_access_token ( )
161- . find_by_token ( token)
162- . await ?
163- . ok_or ( Rejection :: UnknownAccessToken ) ?;
164-
165- // Look for the associated session in the database
166- let session = repo
167- . oauth2_session ( )
168- . lookup ( token. session_id )
169- . await ?
170- . ok_or_else ( || Rejection :: LoadSession ( token. session_id ) ) ?;
171-
172- // Record the activity on the session
173- activity_tracker
174- . record_oauth2_session ( & clock, & session)
175- . await ;
224+ CallerSession :: PersonalSession ( session)
225+ }
226+ _other => {
227+ return Err ( Rejection :: InvalidAccessTokenType ) ;
228+ }
229+ } ;
176230
177231 // Load the user if there is one
178- let user = if let Some ( user_id) = session. user_id {
232+ let user = if let Some ( user_id) = session. user_id ( ) {
179233 let user = repo
180234 . user ( )
181235 . lookup ( user_id)
@@ -193,17 +247,9 @@ where
193247 return Err ( Rejection :: UserLocked ) ;
194248 }
195249
196- if !session. is_valid ( ) {
197- return Err ( Rejection :: SessionRevoked ) ;
198- }
199-
200- if !token. is_valid ( clock. now ( ) ) {
201- return Err ( Rejection :: TokenExpired ) ;
202- }
203-
204250 // For now, we only check that the session has the admin scope
205251 // Later we might want to check other route-specific scopes
206- if !session. scope . contains ( "urn:mas:admin" ) {
252+ if !session. scope ( ) . contains ( "urn:mas:admin" ) {
207253 return Err ( Rejection :: MissingScope ) ;
208254 }
209255
@@ -215,3 +261,26 @@ where
215261 } )
216262 }
217263}
264+
265+ /// The session representing the caller of the Admin API;
266+ /// could either be an OAuth session or a personal session.
267+ pub enum CallerSession {
268+ OAuth2Session ( Session ) ,
269+ PersonalSession ( PersonalSession ) ,
270+ }
271+
272+ impl CallerSession {
273+ pub fn scope ( & self ) -> & Scope {
274+ match self {
275+ CallerSession :: OAuth2Session ( session) => & session. scope ,
276+ CallerSession :: PersonalSession ( session) => & session. scope ,
277+ }
278+ }
279+
280+ pub fn user_id ( & self ) -> Option < Ulid > {
281+ match self {
282+ CallerSession :: OAuth2Session ( session) => session. user_id ,
283+ CallerSession :: PersonalSession ( session) => Some ( session. actor_user_id ) ,
284+ }
285+ }
286+ }
0 commit comments