@clerk/shared
Version:
Internal package utils used by the Clerk SDKs
1 lines • 19.2 kB
Source Map (JSON)
{"version":3,"file":"authorization.mjs","names":[],"sources":["../src/authorization.ts"],"sourcesContent":["import type {\n ActClaim,\n CheckAuthorizationWithCustomPermissions,\n GetToken,\n JwtPayload,\n OrganizationCustomPermissionKey,\n OrganizationCustomRoleKey,\n PendingSessionOptions,\n ReverificationConfig,\n SessionStatusClaim,\n SessionVerificationLevel,\n SessionVerificationTypes,\n SignOut,\n UseAuthReturn,\n} from './types';\n\ntype TypesToConfig = Record<SessionVerificationTypes, Exclude<ReverificationConfig, SessionVerificationTypes>>;\ntype AuthorizationOptions = {\n userId: string | null | undefined;\n orgId: string | null | undefined;\n orgRole: string | null | undefined;\n orgPermissions: string[] | null | undefined;\n factorVerificationAge: [number, number] | null;\n features: string | null | undefined;\n plans: string | null | undefined;\n};\n\n// Internal verdict for each authorization dimension.\n// pass = caller asked, the dimension is satisfied\n// fail = caller asked, the dimension is not satisfied (includes \"data missing\" - fail closed)\n// skip = caller did not ask in this dimension; it does not contribute to the result\ntype CheckResult = 'pass' | 'fail' | 'skip';\n\ntype CheckOrgAuthorization = (\n params: { role?: OrganizationCustomRoleKey; permission?: OrganizationCustomPermissionKey },\n options: Pick<AuthorizationOptions, 'orgId' | 'orgRole' | 'orgPermissions'>,\n) => CheckResult;\n\ntype CheckBillingAuthorization = (\n params: { feature?: string; plan?: string },\n options: Pick<AuthorizationOptions, 'plans' | 'features'>,\n) => CheckResult;\n\ntype CheckReverificationAuthorization = (\n params: {\n reverification?: ReverificationConfig;\n },\n { factorVerificationAge }: AuthorizationOptions,\n) => CheckResult;\n\nconst TYPES_TO_OBJECTS: TypesToConfig = {\n strict_mfa: {\n afterMinutes: 10,\n level: 'multi_factor',\n },\n strict: {\n afterMinutes: 10,\n level: 'second_factor',\n },\n moderate: {\n afterMinutes: 60,\n level: 'second_factor',\n },\n lax: {\n afterMinutes: 1_440,\n level: 'second_factor',\n },\n};\n\nconst ALLOWED_LEVELS = new Set<SessionVerificationLevel>(['first_factor', 'second_factor', 'multi_factor']);\n\nconst ALLOWED_TYPES = new Set<SessionVerificationTypes>(['strict_mfa', 'strict', 'moderate', 'lax']);\n\nconst ORG_SCOPES = new Set(['o', 'org', 'organization']);\nconst USER_SCOPES = new Set(['u', 'user']);\n\n// Helper functions\nconst isValidMaxAge = (maxAge: any) => typeof maxAge === 'number' && maxAge > 0;\nconst isValidLevel = (level: any) => ALLOWED_LEVELS.has(level);\nconst isValidVerificationType = (type: any) => ALLOWED_TYPES.has(type);\nconst isValidFactorAge = (x: unknown): x is number =>\n typeof x === 'number' && Number.isFinite(x) && (x === -1 || x >= 0);\n\nconst prefixWithOrg = (value: string) => value.replace(/^(org:)*/, 'org:');\n\n/**\n * Checks if a user has the required organization-level authorization.\n * If both role and permission are provided, both must match (AND).\n */\nconst checkOrgAuthorization: CheckOrgAuthorization = (params, options) => {\n const { orgId, orgRole, orgPermissions } = options;\n const roleAsked = params.role !== undefined;\n const permissionAsked = params.permission !== undefined;\n\n if (!roleAsked && !permissionAsked) {\n return 'skip';\n }\n\n // Asked with a non-string value (e.g. null cast through `as any`): fail closed\n // rather than letting `prefixWithOrg` throw.\n if (roleAsked && typeof params.role !== 'string') {\n return 'fail';\n }\n if (permissionAsked && typeof params.permission !== 'string') {\n return 'fail';\n }\n\n if (!orgId) {\n return 'fail';\n }\n\n if (roleAsked) {\n if (typeof orgRole !== 'string' || !orgRole) {\n return 'fail';\n }\n if (prefixWithOrg(orgRole) !== prefixWithOrg(params.role as string)) {\n return 'fail';\n }\n }\n\n if (permissionAsked) {\n if (!Array.isArray(orgPermissions)) {\n return 'fail';\n }\n if (!orgPermissions.includes(prefixWithOrg(params.permission as string))) {\n return 'fail';\n }\n }\n\n return 'pass';\n};\n\nconst checkForFeatureOrPlan = (claim: string, featureOrPlan: string) => {\n const { org: orgFeatures, user: userFeatures } = splitByScope(claim);\n const [rawScope, rawId] = featureOrPlan.split(':');\n const hasExplicitScope = rawId !== undefined;\n const scope = rawScope;\n const id = rawId || rawScope;\n\n if (hasExplicitScope && !ORG_SCOPES.has(scope) && !USER_SCOPES.has(scope)) {\n throw new Error(`Invalid scope: ${scope}`);\n }\n\n if (hasExplicitScope) {\n if (ORG_SCOPES.has(scope)) {\n return orgFeatures.includes(id);\n }\n if (USER_SCOPES.has(scope)) {\n return userFeatures.includes(id);\n }\n }\n\n // Since org scoped features will not exist if there is not an active org, merging is safe.\n return [...orgFeatures, ...userFeatures].includes(id);\n};\n\n/**\n * Checks if a user is entitled to the requested feature or plan.\n * If both feature and plan are provided, both must match (AND).\n */\nconst checkBillingAuthorization: CheckBillingAuthorization = (params, options) => {\n const { features, plans } = options;\n const featureAsked = params.feature !== undefined;\n const planAsked = params.plan !== undefined;\n\n if (!featureAsked && !planAsked) {\n return 'skip';\n }\n\n // Asked with a non-string value: fail closed before handing to checkForFeatureOrPlan.\n if (featureAsked && typeof params.feature !== 'string') {\n return 'fail';\n }\n if (planAsked && typeof params.plan !== 'string') {\n return 'fail';\n }\n\n if (featureAsked) {\n if (typeof features !== 'string' || !features) {\n return 'fail';\n }\n try {\n if (!checkForFeatureOrPlan(features, params.feature as string)) {\n return 'fail';\n }\n } catch {\n return 'fail';\n }\n }\n\n if (planAsked) {\n if (typeof plans !== 'string' || !plans) {\n return 'fail';\n }\n try {\n if (!checkForFeatureOrPlan(plans, params.plan as string)) {\n return 'fail';\n }\n } catch {\n return 'fail';\n }\n }\n\n return 'pass';\n};\n\nconst splitByScope = (fea: string | null | undefined) => {\n const org: string[] = [];\n const user: string[] = [];\n\n if (!fea) {\n return { org, user };\n }\n\n const parts = fea.split(',');\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i].trim();\n const colonIndex = part.indexOf(':');\n if (colonIndex === -1) {\n throw new Error(`Invalid claim element (missing colon): ${part}`);\n }\n const scope = part.slice(0, colonIndex);\n const value = part.slice(colonIndex + 1);\n\n if (scope === 'o') {\n org.push(value);\n } else if (scope === 'u') {\n user.push(value);\n } else if (scope === 'ou' || scope === 'uo') {\n org.push(value);\n user.push(value);\n }\n }\n\n return { org, user };\n};\n\nconst validateReverificationConfig = (config: ReverificationConfig | undefined | null) => {\n if (!config) {\n return false;\n }\n\n const convertConfigToObject = (config: ReverificationConfig) => {\n if (typeof config === 'string') {\n return TYPES_TO_OBJECTS[config];\n }\n return config;\n };\n\n const isValidStringValue = typeof config === 'string' && isValidVerificationType(config);\n const isValidObjectValue =\n typeof config === 'object' && isValidLevel(config.level) && isValidMaxAge(config.afterMinutes);\n\n if (isValidStringValue || isValidObjectValue) {\n return convertConfigToObject.bind(null, config);\n }\n\n return false;\n};\n\n/**\n * Evaluates if the user meets re-verification authentication requirements.\n * Handles different verification levels (first factor, second factor, multi-factor).\n */\nconst checkReverificationAuthorization: CheckReverificationAuthorization = (params, { factorVerificationAge }) => {\n if (params.reverification === undefined) {\n return 'skip';\n }\n\n if (!factorVerificationAge) {\n return 'fail';\n }\n\n // Validate the tuple shape before comparing ages to defend against malformed JWT\n // payloads or TS `as any` escapes. `factor1Age` / `factor2Age` must be numbers\n // representing minutes (or -1 when a factor group is not enabled).\n if (\n !Array.isArray(factorVerificationAge) ||\n factorVerificationAge.length !== 2 ||\n !isValidFactorAge(factorVerificationAge[0]) ||\n !isValidFactorAge(factorVerificationAge[1])\n ) {\n return 'fail';\n }\n\n const getConfig = validateReverificationConfig(params.reverification);\n if (!getConfig) {\n return 'fail';\n }\n\n const { level, afterMinutes } = getConfig();\n const [factor1Age, factor2Age] = factorVerificationAge;\n\n // -1 indicates the factor group (1fa, 2fa) is not enabled.\n // If neither factor is enrolled we cannot verify anything; fail closed.\n if (factor1Age === -1 && factor2Age === -1) {\n return 'fail';\n }\n\n const factor1FreshEnough = factor1Age !== -1 && afterMinutes > factor1Age;\n const factor2FreshEnough = factor2Age !== -1 && afterMinutes > factor2Age;\n\n switch (level) {\n case 'first_factor':\n return factor1FreshEnough ? 'pass' : 'fail';\n case 'second_factor':\n // Graceful downgrade: prefer second factor; fall back to whichever factor is\n // enrolled when the other is missing.\n if (factor2Age === -1) {\n return factor1FreshEnough ? 'pass' : 'fail';\n }\n if (factor1Age === -1) {\n return factor2FreshEnough ? 'pass' : 'fail';\n }\n return factor2FreshEnough ? 'pass' : 'fail';\n case 'multi_factor':\n // Graceful downgrade: no second factor enrolled falls back to first factor.\n if (factor2Age === -1) {\n return factor1FreshEnough ? 'pass' : 'fail';\n }\n // Second factor exists but first factor is not enrolled - we cannot satisfy\n // the multi-factor requirement.\n if (factor1Age === -1) {\n return 'fail';\n }\n return factor1FreshEnough && factor2FreshEnough ? 'pass' : 'fail';\n }\n};\n\n// At least one dimension must have passed, and every non-skip result must be a pass.\n// This is an AND across asked dimensions with a fail-closed default: if a helper ever\n// returns anything other than 'pass' or 'skip' (a typo, off-type, or future variant),\n// it is treated as a denial.\nconst combine = (results: CheckResult[]): boolean =>\n results.some(r => r === 'pass') && results.every(r => r === 'pass' || r === 'skip');\n\n/**\n * Creates a function for comprehensive user authorization checks.\n * Combines organization, billing, and reverification checks. The returned function\n * authorizes only when every requested dimension passes; any requested dimension\n * that cannot be satisfied (including missing or malformed session data) denies\n * the request. Fails if `userId` is missing.\n */\nconst createCheckAuthorization = (options: AuthorizationOptions): CheckAuthorizationWithCustomPermissions => {\n return (params): boolean => {\n if (!options.userId) {\n return false;\n }\n\n return combine([\n checkOrgAuthorization(params, options),\n checkBillingAuthorization(params, options),\n checkReverificationAuthorization(params, options),\n ]);\n };\n};\n\ntype AuthStateOptions = {\n authObject: {\n userId?: string | null;\n sessionId?: string | null;\n sessionStatus?: SessionStatusClaim | null;\n sessionClaims?: JwtPayload | null;\n actor?: ActClaim | null;\n orgId?: string | null;\n orgRole?: OrganizationCustomRoleKey | null;\n orgSlug?: string | null;\n orgPermissions?: OrganizationCustomPermissionKey[] | null;\n getToken: GetToken;\n signOut: SignOut;\n has: (params: Parameters<CheckAuthorizationWithCustomPermissions>[0]) => boolean;\n };\n options: PendingSessionOptions;\n};\n\n/**\n * Shared utility function that centralizes auth state resolution logic,\n * preventing duplication across different packages.\n *\n * @internal\n */\nconst resolveAuthState = ({\n authObject: {\n sessionId,\n sessionStatus,\n userId,\n actor,\n orgId,\n orgRole,\n orgSlug,\n signOut,\n getToken,\n has,\n sessionClaims,\n },\n options: { treatPendingAsSignedOut = true },\n}: AuthStateOptions): UseAuthReturn | undefined => {\n if (sessionId === undefined && userId === undefined) {\n return {\n actor: undefined,\n getToken,\n has: () => false,\n isLoaded: false,\n isSignedIn: undefined,\n orgId: undefined,\n orgRole: undefined,\n orgSlug: undefined,\n sessionClaims: undefined,\n sessionId,\n signOut,\n userId,\n } as const;\n }\n\n if (sessionId === null && userId === null) {\n return {\n actor: null,\n getToken,\n has: () => false,\n isLoaded: true,\n isSignedIn: false,\n orgId: null,\n orgRole: null,\n orgSlug: null,\n sessionClaims: null,\n sessionId,\n signOut,\n userId,\n } as const;\n }\n\n if (treatPendingAsSignedOut && sessionStatus === 'pending') {\n return {\n actor: null,\n getToken,\n has: () => false,\n isLoaded: true,\n isSignedIn: false,\n orgId: null,\n orgRole: null,\n orgSlug: null,\n sessionClaims: null,\n sessionId: null,\n signOut,\n userId: null,\n } as const;\n }\n\n if (!!sessionId && !!sessionClaims && !!userId && !!orgId && !!orgRole) {\n return {\n actor: actor || null,\n getToken,\n has,\n isLoaded: true,\n isSignedIn: true,\n orgId,\n orgRole,\n orgSlug: orgSlug || null,\n sessionClaims,\n sessionId,\n signOut,\n userId,\n } as const;\n }\n\n if (!!sessionId && !!sessionClaims && !!userId && !orgId) {\n return {\n actor: actor || null,\n getToken,\n has,\n isLoaded: true,\n isSignedIn: true,\n orgId: null,\n orgRole: null,\n orgSlug: null,\n sessionClaims,\n sessionId,\n signOut,\n userId,\n } as const;\n }\n};\n\nexport { createCheckAuthorization, resolveAuthState, splitByScope, validateReverificationConfig };\n"],"mappings":";AAkDA,MAAM,mBAAkC;CACtC,YAAY;EACV,cAAc;EACd,OAAO;CACT;CACA,QAAQ;EACN,cAAc;EACd,OAAO;CACT;CACA,UAAU;EACR,cAAc;EACd,OAAO;CACT;CACA,KAAK;EACH,cAAc;EACd,OAAO;CACT;AACF;AAEA,MAAM,iBAAiB,IAAI,IAA8B;CAAC;CAAgB;CAAiB;AAAc,CAAC;AAE1G,MAAM,gBAAgB,IAAI,IAA8B;CAAC;CAAc;CAAU;CAAY;AAAK,CAAC;AAEnG,MAAM,aAAa,IAAI,IAAI;CAAC;CAAK;CAAO;AAAc,CAAC;AACvD,MAAM,cAAc,IAAI,IAAI,CAAC,KAAK,MAAM,CAAC;AAGzC,MAAM,iBAAiB,WAAgB,OAAO,WAAW,YAAY,SAAS;AAC9E,MAAM,gBAAgB,UAAe,eAAe,IAAI,KAAK;AAC7D,MAAM,2BAA2B,SAAc,cAAc,IAAI,IAAI;AACrE,MAAM,oBAAoB,MACxB,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,MAAM,MAAM,MAAM,KAAK;AAEnE,MAAM,iBAAiB,UAAkB,MAAM,QAAQ,YAAY,MAAM;;;;;AAMzE,MAAM,yBAAgD,QAAQ,YAAY;CACxE,MAAM,EAAE,OAAO,SAAS,mBAAmB;CAC3C,MAAM,YAAY,OAAO,SAAS;CAClC,MAAM,kBAAkB,OAAO,eAAe;CAE9C,IAAI,CAAC,aAAa,CAAC,iBACjB,OAAO;CAKT,IAAI,aAAa,OAAO,OAAO,SAAS,UACtC,OAAO;CAET,IAAI,mBAAmB,OAAO,OAAO,eAAe,UAClD,OAAO;CAGT,IAAI,CAAC,OACH,OAAO;CAGT,IAAI,WAAW;EACb,IAAI,OAAO,YAAY,YAAY,CAAC,SAClC,OAAO;EAET,IAAI,cAAc,OAAO,MAAM,cAAc,OAAO,IAAc,GAChE,OAAO;CAEX;CAEA,IAAI,iBAAiB;EACnB,IAAI,CAAC,MAAM,QAAQ,cAAc,GAC/B,OAAO;EAET,IAAI,CAAC,eAAe,SAAS,cAAc,OAAO,UAAoB,CAAC,GACrE,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,yBAAyB,OAAe,kBAA0B;CACtE,MAAM,EAAE,KAAK,aAAa,MAAM,iBAAiB,aAAa,KAAK;CACnE,MAAM,CAAC,UAAU,SAAS,cAAc,MAAM,GAAG;CACjD,MAAM,mBAAmB,UAAU;CACnC,MAAM,QAAQ;CACd,MAAM,KAAK,SAAS;CAEpB,IAAI,oBAAoB,CAAC,WAAW,IAAI,KAAK,KAAK,CAAC,YAAY,IAAI,KAAK,GACtE,MAAM,IAAI,MAAM,kBAAkB,OAAO;CAG3C,IAAI,kBAAkB;EACpB,IAAI,WAAW,IAAI,KAAK,GACtB,OAAO,YAAY,SAAS,EAAE;EAEhC,IAAI,YAAY,IAAI,KAAK,GACvB,OAAO,aAAa,SAAS,EAAE;CAEnC;CAGA,OAAO,CAAC,GAAG,aAAa,GAAG,YAAY,CAAC,CAAC,SAAS,EAAE;AACtD;;;;;AAMA,MAAM,6BAAwD,QAAQ,YAAY;CAChF,MAAM,EAAE,UAAU,UAAU;CAC5B,MAAM,eAAe,OAAO,YAAY;CACxC,MAAM,YAAY,OAAO,SAAS;CAElC,IAAI,CAAC,gBAAgB,CAAC,WACpB,OAAO;CAIT,IAAI,gBAAgB,OAAO,OAAO,YAAY,UAC5C,OAAO;CAET,IAAI,aAAa,OAAO,OAAO,SAAS,UACtC,OAAO;CAGT,IAAI,cAAc;EAChB,IAAI,OAAO,aAAa,YAAY,CAAC,UACnC,OAAO;EAET,IAAI;GACF,IAAI,CAAC,sBAAsB,UAAU,OAAO,OAAiB,GAC3D,OAAO;EAEX,QAAQ;GACN,OAAO;EACT;CACF;CAEA,IAAI,WAAW;EACb,IAAI,OAAO,UAAU,YAAY,CAAC,OAChC,OAAO;EAET,IAAI;GACF,IAAI,CAAC,sBAAsB,OAAO,OAAO,IAAc,GACrD,OAAO;EAEX,QAAQ;GACN,OAAO;EACT;CACF;CAEA,OAAO;AACT;AAEA,MAAM,gBAAgB,QAAmC;CACvD,MAAM,MAAgB,CAAC;CACvB,MAAM,OAAiB,CAAC;CAExB,IAAI,CAAC,KACH,OAAO;EAAE;EAAK;CAAK;CAGrB,MAAM,QAAQ,IAAI,MAAM,GAAG;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM,EAAE,CAAC,KAAK;EAC3B,MAAM,aAAa,KAAK,QAAQ,GAAG;EACnC,IAAI,eAAe,IACjB,MAAM,IAAI,MAAM,0CAA0C,MAAM;EAElE,MAAM,QAAQ,KAAK,MAAM,GAAG,UAAU;EACtC,MAAM,QAAQ,KAAK,MAAM,aAAa,CAAC;EAEvC,IAAI,UAAU,KACZ,IAAI,KAAK,KAAK;OACT,IAAI,UAAU,KACnB,KAAK,KAAK,KAAK;OACV,IAAI,UAAU,QAAQ,UAAU,MAAM;GAC3C,IAAI,KAAK,KAAK;GACd,KAAK,KAAK,KAAK;EACjB;CACF;CAEA,OAAO;EAAE;EAAK;CAAK;AACrB;AAEA,MAAM,gCAAgC,WAAoD;CACxF,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,yBAAyB,WAAiC;EAC9D,IAAI,OAAO,WAAW,UACpB,OAAO,iBAAiB;EAE1B,OAAO;CACT;CAEA,MAAM,qBAAqB,OAAO,WAAW,YAAY,wBAAwB,MAAM;CACvF,MAAM,qBACJ,OAAO,WAAW,YAAY,aAAa,OAAO,KAAK,KAAK,cAAc,OAAO,YAAY;CAE/F,IAAI,sBAAsB,oBACxB,OAAO,sBAAsB,KAAK,MAAM,MAAM;CAGhD,OAAO;AACT;;;;;AAMA,MAAM,oCAAsE,QAAQ,EAAE,4BAA4B;CAChH,IAAI,OAAO,mBAAmB,QAC5B,OAAO;CAGT,IAAI,CAAC,uBACH,OAAO;CAMT,IACE,CAAC,MAAM,QAAQ,qBAAqB,KACpC,sBAAsB,WAAW,KACjC,CAAC,iBAAiB,sBAAsB,EAAE,KAC1C,CAAC,iBAAiB,sBAAsB,EAAE,GAE1C,OAAO;CAGT,MAAM,YAAY,6BAA6B,OAAO,cAAc;CACpE,IAAI,CAAC,WACH,OAAO;CAGT,MAAM,EAAE,OAAO,iBAAiB,UAAU;CAC1C,MAAM,CAAC,YAAY,cAAc;CAIjC,IAAI,eAAe,MAAM,eAAe,IACtC,OAAO;CAGT,MAAM,qBAAqB,eAAe,MAAM,eAAe;CAC/D,MAAM,qBAAqB,eAAe,MAAM,eAAe;CAE/D,QAAQ,OAAR;EACE,KAAK,gBACH,OAAO,qBAAqB,SAAS;EACvC,KAAK;GAGH,IAAI,eAAe,IACjB,OAAO,qBAAqB,SAAS;GAEvC,IAAI,eAAe,IACjB,OAAO,qBAAqB,SAAS;GAEvC,OAAO,qBAAqB,SAAS;EACvC,KAAK;GAEH,IAAI,eAAe,IACjB,OAAO,qBAAqB,SAAS;GAIvC,IAAI,eAAe,IACjB,OAAO;GAET,OAAO,sBAAsB,qBAAqB,SAAS;CAC/D;AACF;AAMA,MAAM,WAAW,YACf,QAAQ,MAAK,MAAK,MAAM,MAAM,KAAK,QAAQ,OAAM,MAAK,MAAM,UAAU,MAAM,MAAM;;;;;;;;AASpF,MAAM,4BAA4B,YAA2E;CAC3G,QAAQ,WAAoB;EAC1B,IAAI,CAAC,QAAQ,QACX,OAAO;EAGT,OAAO,QAAQ;GACb,sBAAsB,QAAQ,OAAO;GACrC,0BAA0B,QAAQ,OAAO;GACzC,iCAAiC,QAAQ,OAAO;EAClD,CAAC;CACH;AACF;;;;;;;AA0BA,MAAM,oBAAoB,EACxB,YAAY,EACV,WACA,eACA,QACA,OACA,OACA,SACA,SACA,SACA,UACA,KACA,iBAEF,SAAS,EAAE,0BAA0B,aACY;CACjD,IAAI,cAAc,UAAa,WAAW,QACxC,OAAO;EACL,OAAO;EACP;EACA,WAAW;EACX,UAAU;EACV,YAAY;EACZ,OAAO;EACP,SAAS;EACT,SAAS;EACT,eAAe;EACf;EACA;EACA;CACF;CAGF,IAAI,cAAc,QAAQ,WAAW,MACnC,OAAO;EACL,OAAO;EACP;EACA,WAAW;EACX,UAAU;EACV,YAAY;EACZ,OAAO;EACP,SAAS;EACT,SAAS;EACT,eAAe;EACf;EACA;EACA;CACF;CAGF,IAAI,2BAA2B,kBAAkB,WAC/C,OAAO;EACL,OAAO;EACP;EACA,WAAW;EACX,UAAU;EACV,YAAY;EACZ,OAAO;EACP,SAAS;EACT,SAAS;EACT,eAAe;EACf,WAAW;EACX;EACA,QAAQ;CACV;CAGF,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,iBAAiB,CAAC,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC,SAC7D,OAAO;EACL,OAAO,SAAS;EAChB;EACA;EACA,UAAU;EACV,YAAY;EACZ;EACA;EACA,SAAS,WAAW;EACpB;EACA;EACA;EACA;CACF;CAGF,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC,iBAAiB,CAAC,CAAC,UAAU,CAAC,OACjD,OAAO;EACL,OAAO,SAAS;EAChB;EACA;EACA,UAAU;EACV,YAAY;EACZ,OAAO;EACP,SAAS;EACT,SAAS;EACT;EACA;EACA;EACA;CACF;AAEJ"}