@@ -13,6 +13,20 @@ const PUSH_SCOPE = 'assignment_notifications';
1313const REMOTE_TOKEN_TTL_SECONDS = 12 * 60 * 60 ;
1414const PUSH_TOKEN_TTL_SECONDS = 10 * 60 ;
1515
16+ const getCompetitionManagers = ( competition ) => {
17+ const data = competition ?. competition || competition ;
18+ return [ ...( data ?. organizers || [ ] ) , ...( data ?. delegates || [ ] ) ] ;
19+ } ;
20+
21+ const isListedCompetitionManager = ( competition , userId ) =>
22+ getCompetitionManagers ( competition ) . some ( ( user ) => Number ( user ?. id ) === Number ( userId ) ) ;
23+
24+ const getManagedCompetitionIds = ( competitions , userId ) =>
25+ competitions
26+ . filter ( ( competition ) => isListedCompetitionManager ( competition , userId ) )
27+ . map ( ( competition ) => competition . id )
28+ . filter ( Boolean ) ;
29+
1630const signJwt = ( claims , secret ) => {
1731 const encodedHeader = base64Url ( JSON . stringify ( { alg : 'HS256' , typ : 'JWT' } ) ) ;
1832 const encodedPayload = base64Url ( JSON . stringify ( claims ) ) ;
@@ -58,6 +72,7 @@ exports.handler = async (event) => {
5872 }
5973
6074 const { accessToken, competitionId, scope } = body ;
75+ const tokenScope = scope === REMOTE_SCOPE ? REMOTE_SCOPE : PUSH_SCOPE ;
6176 if ( ! accessToken ) {
6277 return {
6378 statusCode : 400 ,
@@ -67,7 +82,9 @@ exports.handler = async (event) => {
6782 }
6883
6984 const wcaOrigin = process . env . WCA_ORIGIN || 'https://www.worldcubeassociation.org' ;
70- const meResponse = await fetch ( `${ wcaOrigin } /api/v0/me` , {
85+ const meParams =
86+ tokenScope === REMOTE_SCOPE ? '?upcoming_competitions=true&ongoing_competitions=true' : '' ;
87+ const meResponse = await fetch ( `${ wcaOrigin } /api/v0/me${ meParams } ` , {
7188 headers : {
7289 Authorization : `Bearer ${ accessToken } ` ,
7390 } ,
@@ -81,7 +98,7 @@ exports.handler = async (event) => {
8198 } ;
8299 }
83100
84- const { me } = await meResponse . json ( ) ;
101+ const { me, ongoing_competitions = [ ] , upcoming_competitions = [ ] } = await meResponse . json ( ) ;
85102 if ( ! me ?. id ) {
86103 return {
87104 statusCode : 401 ,
@@ -90,7 +107,6 @@ exports.handler = async (event) => {
90107 } ;
91108 }
92109
93- const tokenScope = scope === REMOTE_SCOPE ? REMOTE_SCOPE : PUSH_SCOPE ;
94110 const tokenTtlSeconds =
95111 tokenScope === REMOTE_SCOPE ? REMOTE_TOKEN_TTL_SECONDS : PUSH_TOKEN_TTL_SECONDS ;
96112 if ( tokenScope === REMOTE_SCOPE && ! competitionId ) {
@@ -101,11 +117,50 @@ exports.handler = async (event) => {
101117 } ;
102118 }
103119
120+ let remoteCompetitionIds = [ ] ;
121+
122+ if ( tokenScope === REMOTE_SCOPE ) {
123+ const competitionResponse = await fetch ( `${ wcaOrigin } /api/v0/competitions/${ competitionId } ` , {
124+ headers : {
125+ Authorization : `Bearer ${ accessToken } ` ,
126+ } ,
127+ } ) ;
128+
129+ if ( ! competitionResponse . ok ) {
130+ return {
131+ statusCode : competitionResponse . status === 404 ? 404 : 502 ,
132+ headers,
133+ body : JSON . stringify ( { message : 'Unable to verify competition remote access' } ) ,
134+ } ;
135+ }
136+
137+ const competition = await competitionResponse . json ( ) ;
138+ if ( ! isListedCompetitionManager ( competition , me . id ) ) {
139+ return {
140+ statusCode : 403 ,
141+ headers,
142+ body : JSON . stringify ( {
143+ message :
144+ 'Only listed competition delegates and organizers can use Live Activities Remote' ,
145+ } ) ,
146+ } ;
147+ }
148+
149+ // Remote tokens are scoped to competitions the WCA API says this user manages. The
150+ // NotifyComp API must reject remote mutations for competition IDs outside this claim.
151+ remoteCompetitionIds = [
152+ ...new Set ( [
153+ competitionId ,
154+ ...getManagedCompetitionIds ( [ ...ongoing_competitions , ...upcoming_competitions ] , me . id ) ,
155+ ] ) ,
156+ ] ;
157+ }
158+
104159 const now = Math . floor ( Date . now ( ) / 1000 ) ;
105160 const token = signJwt (
106161 {
107162 aud : process . env . COMPETITION_GROUPS_JWT_AUDIENCE || 'notifycomp' ,
108- competitionIds : tokenScope === REMOTE_SCOPE ? [ competitionId ] : undefined ,
163+ competitionIds : tokenScope === REMOTE_SCOPE ? remoteCompetitionIds : undefined ,
109164 exp : now + tokenTtlSeconds ,
110165 iat : now ,
111166 iss : process . env . COMPETITION_GROUPS_JWT_ISSUER || 'competitiongroups.com' ,
0 commit comments