All files / users list.js

100% Statements 33/33
85% Branches 17/20
100% Functions 3/3
100% Lines 32/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 1021x 1x 1x   1x 1x   1x 15x 15x 15x 15x   15x 2x                     13x   13x 1x                     12x       12x 11x     11x 3x 3x   6x           11x     11x 11x   11x 11x 11x         11x             11x                         1x 1x                    
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb')
const { DynamoDBDocumentClient, ScanCommand } = require('@aws-sdk/lib-dynamodb')
const { permissionChecker } = require('../utils/PermissionChecker')
 
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
 
exports.handler = async (event) => {
  try {
    const search = event.queryStringParameters?.search || ''
    const tenantId = event.requestContext?.authorizer?.claims?.['custom:tenantId']
    const userId = event.requestContext?.authorizer?.claims?.sub
 
    if (!userId || !tenantId) {
      return {
        statusCode: 401,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ error: 'Unauthorized' })
      }
    }
 
    // Check if user has permission to list users
    const permissionCheck = await permissionChecker.hasPermission(userId, tenantId, 'users:list')
 
    if (!permissionCheck.hasPermission) {
      return {
        statusCode: 403,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ error: 'Forbidden - Insufficient permissions' })
      }
    }
 
    // Scan users table with optional search filter
    const params = {
      TableName: process.env.USERS_TABLE_NAME
    }
 
    const result = await docClient.send(new ScanCommand(params))
    let users = result.Items || []
 
    // Filter by search query if provided (email or name)
    if (search) {
      const searchLower = search.toLowerCase()
      users = users.filter(
        (user) =>
          user.email?.toLowerCase().includes(searchLower) ||
          user.name?.toLowerCase().includes(searchLower)
      )
    }
 
    // Pagination support (in-memory pagination after filtering)
    const limit = event.queryStringParameters?.limit
      ? parseInt(event.queryStringParameters.limit)
      : 30
    const nextToken = event.queryStringParameters?.nextToken
    const offset = nextToken ? parseInt(Buffer.from(nextToken, 'base64').toString()) : 0
 
    const paginatedUsers = users.slice(offset, offset + limit)
    const hasMore = offset + limit < users.length
    const responseNextToken = hasMore
      ? Buffer.from(String(offset + limit)).toString('base64')
      : null
 
    // Return limited user info (no sensitive data)
    const publicUsers = paginatedUsers.map((user) => ({
      id: user.id,
      email: user.email,
      name: user.name,
      status: user.status
    }))
 
    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        items: publicUsers,
        nextToken: responseNextToken,
        hasMore
      })
    }
  } catch (error) {
    console.error('Error listing users:', error)
    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({ error: 'Failed to list users' })
    }
  }
}