diff --git a/package.json b/package.json index 3cb66c4b..e873d49e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "sync-users": "ts-node --transpile-only scripts/sync-users.ts" + "sync-users": "node scripts/sync-users.js" }, "dependencies": { "@aws-sdk/client-s3": "^3.802.0", diff --git a/scripts/sync-users.js b/scripts/sync-users.js new file mode 100644 index 00000000..bb2da019 --- /dev/null +++ b/scripts/sync-users.js @@ -0,0 +1,123 @@ +const { PrismaClient } = require('@prisma/client'); +const bcrypt = require('bcryptjs'); +require('dotenv').config(); + +const prisma = new PrismaClient(); + +async function syncUsers() { + try { + console.log('Starting user sync process...'); + + // Use your existing /api/users endpoint to get users from Keycloak + // This endpoint already exists in your codebase and handles all the Keycloak auth + const apiUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; + console.log(`Fetching users from: ${apiUrl}/api/users`); + + // Get client credentials token for authentication + const tokenResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'client_credentials', + client_id: process.env.KEYCLOAK_CLIENT_ID, + client_secret: process.env.KEYCLOAK_CLIENT_SECRET, + }), + } + ); + + const tokenData = await tokenResponse.json(); + if (!tokenResponse.ok) { + throw new Error(`Failed to get token: ${JSON.stringify(tokenData)}`); + } + + console.log("Token obtained successfully"); + + // Now fetch users from Keycloak + const usersResponse = await fetch( + `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users?briefRepresentation=false`, + { + headers: { + Authorization: `Bearer ${tokenData.access_token}`, + 'Content-Type': 'application/json', + }, + } + ); + + if (!usersResponse.ok) { + throw new Error(`Failed to fetch users: ${usersResponse.status}`); + } + + const users = await usersResponse.json(); + + // Filter out service accounts and users from other realms + const filteredUsers = users.filter((user) => + !user.serviceAccountClientId && // Remove service accounts + (!user.realm || user.realm === process.env.KEYCLOAK_REALM) // Only users from our realm + ); + + console.log(`Fetched ${filteredUsers.length} users from Keycloak API`); + + const results = { + total: filteredUsers.length, + created: 0, + updated: 0, + failed: 0, + }; + + // Process each user from Keycloak + for (const user of filteredUsers) { + try { + // Check if the keycloak ID exists in our database + const existingUser = await prisma.user.findUnique({ + where: { id: user.id }, + }); + + if (existingUser) { + // Update existing user + await prisma.user.update({ + where: { id: user.id }, + data: { + email: user.email, + // Don't update password as it might be locally changed + updatedAt: new Date(), + }, + }); + console.log(`Updated user: ${user.id} (${user.email})`); + results.updated++; + } else { + // Create new user + // Generate a temporary random password + const tempPassword = await bcrypt.hash(Math.random().toString(36).slice(-10), 10); + + await prisma.user.create({ + data: { + id: user.id, // Use the keycloak ID as our primary ID + email: user.email, + password: tempPassword, + createdAt: new Date(), + updatedAt: new Date(), + }, + }); + console.log(`Created new user: ${user.id} (${user.email})`); + results.created++; + } + } catch (error) { + console.error(`Error processing user ${user.id}:`, error); + results.failed++; + } + } + + console.log('Sync completed. Results:', results); + } catch (error) { + console.error('Error syncing users:', error); + } finally { + await prisma.$disconnect(); + } +} + +// Run the sync function +syncUsers(); \ No newline at end of file diff --git a/scripts/sync-users.ts b/scripts/sync-users.ts index 5efa0261..656581a9 100644 --- a/scripts/sync-users.ts +++ b/scripts/sync-users.ts @@ -1,5 +1,6 @@ -import { PrismaClient } from '@prisma/client'; -import bcrypt from 'bcryptjs'; +const { PrismaClient } = require('@prisma/client'); +const bcrypt = require('bcryptjs'); +require('dotenv').config(); const prisma = new PrismaClient(); diff --git a/tsconfig.json b/tsconfig.json index d635317d..b39f71d0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,19 +1,19 @@ { "compilerOptions": { + "target": "es2017", + "module": "commonjs", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, - "target": "ES2024", "skipLibCheck": true, "strict": true, + "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, - "module": "ESNext", - "moduleResolution": "bundler", + "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, - "forceConsistentCasingInFileNames": true, "plugins": [ { "name": "next"