@@ -252,6 +252,7 @@ module.exports = async function (app) {
252252 * /api/v1/user/pat
253253 */
254254 app . post ( '/tokens' , {
255+ preHandler : app . blockPAT ,
255256 config : {
256257 rateLimit : app . config . rate_limits ? { max : 5 , timeWindow : 30000 } : false
257258 } ,
@@ -263,7 +264,10 @@ module.exports = async function (app) {
263264 properties : {
264265 scope : { type : 'string' } ,
265266 expiresAt : { type : 'number' } ,
266- name : { type : 'string' }
267+ name : { type : 'string' } ,
268+ readOnly : { type : 'boolean' } ,
269+ adminOptIn : { type : 'boolean' } ,
270+ teamIds : { type : 'array' , items : { type : 'string' } }
267271 }
268272 } ,
269273 response : {
@@ -279,8 +283,25 @@ module.exports = async function (app) {
279283 const updates = new app . auditLog . formatters . UpdatesCollection ( )
280284 try {
281285 const body = request . body
282- const token = await app . db . controllers . AccessToken . createPersonalAccessToken ( request . session . User , body . scope , body . expiresAt , body . name )
283- // token has already been sanitised via views.AccessToken.personalAccessToken
286+ const teamIds = body . teamIds ?? [ ]
287+ if ( teamIds . length > 0 ) {
288+ for ( const teamId of teamIds ) {
289+ const decodedId = app . db . models . Team . decodeHashid ( teamId )
290+ const membership = decodedId ? await app . db . models . TeamMember . findOne ( { where : { UserId : request . session . User . id , TeamId : decodedId } } ) : null
291+ if ( ! membership ) {
292+ reply . code ( 400 ) . send ( { code : 'invalid_team' , error : `Not a member of team: ${ teamId } ` } )
293+ return
294+ }
295+ }
296+ }
297+ if ( body . adminOptIn === true && ! request . session . User . admin ) {
298+ reply . code ( 403 ) . send ( { code : 'unauthorized' , error : 'Admin access required to set adminOptIn' } )
299+ return
300+ }
301+ const token = await app . db . controllers . AccessToken . createPersonalAccessToken (
302+ request . session . User , body . scope , body . expiresAt , body . name ,
303+ { readOnly : body . readOnly , adminOptIn : body . adminOptIn , teamIds }
304+ )
284305 updates . push ( 'id' , token . id )
285306 updates . push ( 'name' , token . name )
286307 updates . push ( 'scope' , body . scope )
@@ -342,6 +363,7 @@ module.exports = async function (app) {
342363 * /api/v1/user/tokens/:id
343364 */
344365 app . put ( '/tokens/:id' , {
366+ preHandler : app . blockPAT ,
345367 schema : {
346368 summary : 'Update users Personal Access Token' ,
347369 tags : [ 'Tokens' ] ,
@@ -355,7 +377,10 @@ module.exports = async function (app) {
355377 type : 'object' ,
356378 properties : {
357379 scope : { type : 'string' } ,
358- expiresAt : { type : 'number' }
380+ expiresAt : { type : 'number' } ,
381+ readOnly : { type : 'boolean' } ,
382+ adminOptIn : { type : 'boolean' } ,
383+ teamIds : { type : 'array' , items : { type : 'string' } }
359384 }
360385 } ,
361386 response : {
@@ -372,11 +397,31 @@ module.exports = async function (app) {
372397 try {
373398 const oldToken = await app . db . models . AccessToken . byId ( request . params . id , 'user' , request . session . User . id )
374399 if ( oldToken ) {
400+ const oldSummary = app . db . views . AccessToken . personalAccessTokenSummary ( oldToken )
375401 const body = request . body
376- const newToken = await app . db . controllers . AccessToken . updatePersonalAccessToken ( request . session . User , request . params . id , body . scope , body . expiresAt )
377- updates . pushDifferences ( oldToken , newToken )
402+ const teamIds = body . teamIds
403+ if ( teamIds !== undefined && teamIds . length > 0 ) {
404+ for ( const teamId of teamIds ) {
405+ const decodedId = app . db . models . Team . decodeHashid ( teamId )
406+ const membership = decodedId ? await app . db . models . TeamMember . findOne ( { where : { UserId : request . session . User . id , TeamId : decodedId } } ) : null
407+ if ( ! membership ) {
408+ reply . code ( 400 ) . send ( { code : 'invalid_team' , error : `Not a member of team: ${ teamId } ` } )
409+ return
410+ }
411+ }
412+ }
413+ if ( body . adminOptIn === true && ! request . session . User . admin ) {
414+ reply . code ( 403 ) . send ( { code : 'unauthorized' , error : 'Admin access required to set adminOptIn' } )
415+ return
416+ }
417+ const newToken = await app . db . controllers . AccessToken . updatePersonalAccessToken (
418+ request . session . User , request . params . id , body . scope , body . expiresAt ,
419+ { readOnly : body . readOnly , adminOptIn : body . adminOptIn , teamIds }
420+ )
421+ const newSummary = app . db . views . AccessToken . personalAccessTokenSummary ( newToken )
422+ updates . pushDifferences ( oldSummary , newSummary )
378423 await app . auditLog . User . user . pat . updated ( request . session . User , null , updates )
379- reply . send ( app . db . views . AccessToken . personalAccessTokenSummary ( newToken ) )
424+ reply . send ( newSummary )
380425 return
381426 }
382427 reply . code ( 404 ) . send ( { code : 'not_found' , error : 'Not Found' } )
0 commit comments