This commit is contained in:
alma 2025-05-04 22:20:29 +02:00
parent 26ec4864cf
commit 692c72481f
4 changed files with 131 additions and 7 deletions

View File

@ -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",

123
scripts/sync-users.js Normal file
View File

@ -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();

View File

@ -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();

View File

@ -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"