Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
claims-extractor.ts7.58 kB
/** * Extract claims from user object using dot notation path * * Supports nested paths and handles various claim formats from different * identity providers. Automatically converts non-string values and extracts * from common object patterns. * * **Supported Formats**: * - Arrays of strings: `["admin", "user"]` * - Single string: `"admin"` * - Numbers/booleans: Converted to strings with warning * - Objects: Extracts `name`, `value`, or `id` fields * * **Nested Paths**: Use dot notation for nested claims: * - `"roles"` → `user.roles` * - `"custom.roles"` → `user.custom.roles` * - `"app_metadata.permissions"` → `user.app_metadata.permissions` * * @param user - User object from Passport (typically contains JWT claims) * @param claimPath - Dot-separated path to claims * * @returns Array of claim values as strings * * @example * // Simple array of roles * const user = { roles: ['admin', 'editor'] }; * const claims = extractClaims(user, 'roles'); * console.log(claims); // ['admin', 'editor'] * * @example * // Nested path * const user = { custom: { permissions: ['read', 'write'] } }; * const claims = extractClaims(user, 'custom.permissions'); * console.log(claims); // ['read', 'write'] * * @example * // Object array with name field * const user = { * groups: [ * { name: 'developers', id: 123 }, * { name: 'admins', id: 456 } * ] * }; * const claims = extractClaims(user, 'groups'); * console.log(claims); // ['developers', 'admins'] */ export function extractClaims(user: any, claimPath: string): string[] { if (!user) { return []; } // Support nested paths like "custom.roles" or "groups" const parts = claimPath.split('.'); let value = user; for (const part of parts) { value = value?.[part]; if (value === undefined || value === null) { return []; } } // Handle array or single value if (Array.isArray(value)) { const claims: string[] = []; for (const [i, item] of value.entries()) { // Accept strings directly if (typeof item === 'string') { claims.push(item); continue; } // Convert numbers to strings (some IdPs send role IDs as numbers) if (typeof item === 'number') { console.warn(`[Claims] Converting numeric claim to string: ${item} (at ${claimPath}[${i}])`); claims.push(String(item)); continue; } // Convert booleans to strings (rare but possible) if (typeof item === 'boolean') { console.warn(`[Claims] Converting boolean claim to string: ${item} (at ${claimPath}[${i}])`); claims.push(String(item)); continue; } // Handle objects - try to extract a string representation if (typeof item === 'object' && item !== null) { // Check for common object patterns if ('name' in item && typeof item.name === 'string') { console.warn(`[Claims] Extracting 'name' field from object claim: ${item.name} (at ${claimPath}[${i}])`); claims.push(item.name); continue; } if ('value' in item && typeof item.value === 'string') { console.warn(`[Claims] Extracting 'value' field from object claim: ${item.value} (at ${claimPath}[${i}])`); claims.push(item.value); continue; } if ('id' in item && typeof item.id === 'string') { console.warn(`[Claims] Extracting 'id' field from object claim: ${item.id} (at ${claimPath}[${i}])`); claims.push(item.id); continue; } // Last resort: JSON stringify (not ideal but better than silently dropping) console.error(`[Claims] Unable to extract string from object claim, using JSON representation (at ${claimPath}[${i}]):`, item); claims.push(JSON.stringify(item)); continue; } // Unsupported type - log error and skip console.error(`[Claims] Unsupported claim type '${typeof item}' at ${claimPath}[${i}], value:`, item); console.error(`[Claims] This claim will be IGNORED. User may lack expected permissions.`); } if (claims.length < value.length) { console.warn(`[Claims] Extracted ${claims.length} of ${value.length} claims from ${claimPath}`); console.warn(`[Claims] ${value.length - claims.length} claims were dropped due to unsupported types`); } return claims; } // Handle single value if (typeof value === 'string') { return [value]; } // Convert single number to string if (typeof value === 'number') { console.warn(`[Claims] Converting single numeric claim to string: ${value} (at ${claimPath})`); return [String(value)]; } // Convert single boolean to string if (typeof value === 'boolean') { console.warn(`[Claims] Converting single boolean claim to string: ${value} (at ${claimPath})`); return [String(value)]; } // Handle single object // Note: typeof null === 'object' in JavaScript, so we need the null check for type narrowing if (typeof value === 'object' && value !== null) { // Check for common object patterns if ('name' in value && typeof value.name === 'string') { console.warn(`[Claims] Extracting 'name' field from single object claim: ${value.name} (at ${claimPath})`); return [value.name]; } if ('value' in value && typeof value.value === 'string') { console.warn(`[Claims] Extracting 'value' field from single object claim: ${value.value} (at ${claimPath})`); return [value.value]; } if ('id' in value && typeof value.id === 'string') { console.warn(`[Claims] Extracting 'id' field from single object claim: ${value.id} (at ${claimPath})`); return [value.id]; } // Last resort: JSON stringify console.error(`[Claims] Unable to extract string from single object claim, using JSON representation (at ${claimPath}):`, value); return [JSON.stringify(value)]; } // Unsupported type console.error(`[Claims] Unsupported claim type '${typeof value}' at ${claimPath}, value:`, value); console.error(`[Claims] No claims extracted. User may lack expected permissions.`); return []; } /** * Extract roles from user and add default role if none found * * Convenience wrapper around extractClaims() that provides a fallback * default role when no roles are found in the user object. Useful for * ensuring all users have at least one role for RBAC. * * @param user - User object from Passport * @param claimPath - Path to roles in user object (dot notation) * @param defaultRole - Default role to assign if no roles found * * @returns Array of roles (includes default if no roles extracted) * * @example * // User with roles * const user = { roles: ['admin'] }; * const roles = extractRolesWithDefault(user, 'roles', 'viewer'); * console.log(roles); // ['admin'] * * @example * // User without roles - gets default * const user = { email: 'user@example.com' }; * const roles = extractRolesWithDefault(user, 'roles', 'viewer'); * console.log(roles); // ['viewer'] * * @example * // No default role specified * const user = {}; * const roles = extractRolesWithDefault(user, 'roles'); * console.log(roles); // [] */ export function extractRolesWithDefault( user: any, claimPath: string, defaultRole?: string ): string[] { const roles = extractClaims(user, claimPath); if (roles.length === 0 && defaultRole) { return [defaultRole]; } return roles; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server