Repository: strapi/strapi
File: packages/core/admin/server/src/controllers/authenticated-user.ts (TypeScript)

Code before:
          eeLicense = fs.readFileSync(licensePath).toString();
        } catch (error) {
          // License file doesn't exist or can't be read
        }
      }

      if (!eeLicense) {
        return ctx.badRequest(
          'No EE license found. Please ensure STRAPI_LICENSE environment variable is set or license.txt file exists.'
        );
      }

      const aiServerUrl = process.env.STRAPI_ADMIN_AI_URL || process.env.STRAPI_AI_URL;

      if (!aiServerUrl) {
        return ctx.badRequest(
          'AI server URL not configured. Please set STRAPI_ADMIN_AI_URL or STRAPI_AI_URL environment variable.'
        );
      }

      // Get the current user
      const user = ctx.state.user as AdminUser;

      // Create a secure user identifier
      // Using a hash of email + id + internal salt for enhanced privacy
      const internalSalt = strapi.config.get('uuid', 'default-salt');
      const userIdentifier = crypto
        .createHash('sha256')
        .update(`${user.email}:${user.id}:${internalSalt}`)
        .digest('hex');

      // Get project ID
      const projectId = strapi.config.get('uuid');
      if (!projectId) {
        return ctx.badRequest('Project ID not configured');
      }

      strapi.log.info('Making request to AI server:', {
        url: `${aiServerUrl}/auth/getAiJWT`,
        projectId,
        hasEeLicense: !!eeLicense,
        userEmail: user.email,
      });

      try {
        // Call the AI server's getAiJWT endpoint (now on public server)
        const response = await fetch(`${aiServerUrl}/auth/getAiJWT`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            // No authorization header needed for public endpoint

Review comment (suggestion):
Just a note, but `uuid` here is essentially the strapi `projectId`, not an internal salt.

We shouldn't include email, that actually adds a chance it could be brute-forced out of it, especially since this is hashed and not encrypted. Hashing doesn't really gain us anything in this case.

I think for now we should just use `id` by itself, nothing else, and before release we will run it by Legal to see if it needs to be encrypted (rather than plain text) but afaik the terms of using Strapi AI will include that we transmit the user ids (but no other identifiable info for now)

Code after:
          eeLicense = fs.readFileSync(licensePath).toString();
        } catch (error) {
          // License file doesn't exist or can't be read
        }
      }

      if (!eeLicense) {
        return ctx.badRequest(
          'No EE license found. Please ensure STRAPI_LICENSE environment variable is set or license.txt file exists.'
        );
      }

      const aiServerUrl = process.env.STRAPI_ADMIN_AI_URL || process.env.STRAPI_AI_URL;

      if (!aiServerUrl) {
        return ctx.badRequest(
          'AI server URL not configured. Please set STRAPI_ADMIN_AI_URL or STRAPI_AI_URL environment variable.'
        );
      }

      // Get the current user
      const user = ctx.state.user as AdminUser;

      // Create a secure user identifier using only user ID
      const userIdentifier = user.id.toString();

      // Get project ID
      const projectId = strapi.config.get('uuid');
      if (!projectId) {
        return ctx.badRequest('Project ID not configured');
      }

      strapi.log.info('Making request to AI server:', {
        url: `${aiServerUrl}/auth/getAiJWT`,
        projectId,
        hasEeLicense: !!eeLicense,
        userEmail: user.email,
      });

      try {
        // Call the AI server's getAiJWT endpoint (now on public server)
        const response = await fetch(`${aiServerUrl}/auth/getAiJWT`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            // No authorization header needed for public endpoint
            // Add request ID for tracing
            'X-Request-Id': crypto.randomUUID(),
          },
          body: JSON.stringify({
            eeLicense,