@@ -65,6 +65,9 @@ const ALLOWED_LEVELS = new Set<SessionVerificationLevel>(['first_factor', 'secon
6565
6666const ALLOWED_TYPES = new Set < SessionVerificationTypes > ( [ 'strict_mfa' , 'strict' , 'moderate' , 'lax' ] ) ;
6767
68+ const ORG_SCOPES = new Set ( [ 'o' , 'org' , 'organization' ] ) ;
69+ const USER_SCOPES = new Set ( [ 'u' , 'user' ] ) ;
70+
6871// Helper functions
6972const isValidMaxAge = ( maxAge : any ) => typeof maxAge === 'number' && maxAge > 0 ;
7073const isValidLevel = ( level : any ) => ALLOWED_LEVELS . has ( level ) ;
@@ -100,17 +103,26 @@ const checkOrgAuthorization: CheckOrgAuthorization = (params, options) => {
100103
101104const checkForFeatureOrPlan = ( claim : string , featureOrPlan : string ) => {
102105 const { org : orgFeatures , user : userFeatures } = splitByScope ( claim ) ;
103- const [ scope , _id ] = featureOrPlan . split ( ':' ) ;
104- const id = _id || scope ;
105-
106- if ( scope === 'org' ) {
107- return orgFeatures . includes ( id ) ;
108- } else if ( scope === 'user' ) {
109- return userFeatures . includes ( id ) ;
110- } else {
111- // Since org scoped features will not exist if there is not an active org, merging is safe.
112- return [ ...orgFeatures , ...userFeatures ] . includes ( id ) ;
106+ const [ rawScope , rawId ] = featureOrPlan . split ( ':' ) ;
107+ const hasExplicitScope = rawId !== undefined ;
108+ const scope = rawScope ;
109+ const id = rawId || rawScope ;
110+
111+ if ( hasExplicitScope && ! ORG_SCOPES . has ( scope ) && ! USER_SCOPES . has ( scope ) ) {
112+ throw new Error ( `Invalid scope: ${ scope } ` ) ;
113113 }
114+
115+ if ( hasExplicitScope ) {
116+ if ( ORG_SCOPES . has ( scope ) ) {
117+ return orgFeatures . includes ( id ) ;
118+ }
119+ if ( USER_SCOPES . has ( scope ) ) {
120+ return userFeatures . includes ( id ) ;
121+ }
122+ }
123+
124+ // Since org scoped features will not exist if there is not an active org, merging is safe.
125+ return [ ...orgFeatures , ...userFeatures ] . includes ( id ) ;
114126} ;
115127
116128const checkBillingAuthorization : CheckBillingAuthorization = ( params , options ) => {
@@ -127,13 +139,34 @@ const checkBillingAuthorization: CheckBillingAuthorization = (params, options) =
127139} ;
128140
129141const splitByScope = ( fea : string | null | undefined ) => {
130- const features = fea ? fea . split ( ',' ) . map ( f => f . trim ( ) ) : [ ] ;
142+ const org : string [ ] = [ ] ;
143+ const user : string [ ] = [ ] ;
131144
132- // TODO: make this more efficient
133- return {
134- org : features . filter ( f => f . split ( ':' ) [ 0 ] . includes ( 'o' ) ) . map ( f => f . split ( ':' ) [ 1 ] ) ,
135- user : features . filter ( f => f . split ( ':' ) [ 0 ] . includes ( 'u' ) ) . map ( f => f . split ( ':' ) [ 1 ] ) ,
136- } ;
145+ if ( ! fea ) {
146+ return { org, user } ;
147+ }
148+
149+ const parts = fea . split ( ',' ) ;
150+ for ( let i = 0 ; i < parts . length ; i ++ ) {
151+ const part = parts [ i ] . trim ( ) ;
152+ const colonIndex = part . indexOf ( ':' ) ;
153+ if ( colonIndex === - 1 ) {
154+ throw new Error ( `Invalid claim element (missing colon): ${ part } ` ) ;
155+ }
156+ const scope = part . slice ( 0 , colonIndex ) ;
157+ const value = part . slice ( colonIndex + 1 ) ;
158+
159+ if ( scope === 'o' ) {
160+ org . push ( value ) ;
161+ } else if ( scope === 'u' ) {
162+ user . push ( value ) ;
163+ } else if ( scope === 'ou' || scope === 'uo' ) {
164+ org . push ( value ) ;
165+ user . push ( value ) ;
166+ }
167+ }
168+
169+ return { org, user } ;
137170} ;
138171
139172const validateReverificationConfig = ( config : ReverificationConfig | undefined | null ) => {
0 commit comments