From dc0021dc91c91540c320ddc98b28e79d3c98dc85 Mon Sep 17 00:00:00 2001 From: Alma Date: Thu, 10 Apr 2025 20:19:28 +0200 Subject: [PATCH] add user leantime api --- app/api/users/route.ts | 67 +++++- keycloak-user-creation-workflow.json | 338 +++++++++++++++++++++++++++ 2 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 keycloak-user-creation-workflow.json diff --git a/app/api/users/route.ts b/app/api/users/route.ts index 8db098b2..02c0eb80 100644 --- a/app/api/users/route.ts +++ b/app/api/users/route.ts @@ -200,6 +200,57 @@ function validateUsername(username: string): { isValid: boolean; error?: string return { isValid: true }; } +// Helper function to create user in Leantime +async function createLeantimeUser(userData: { + username: string; + firstName: string; + lastName: string; + email: string; +}): Promise<{ success: boolean; error?: string }> { + try { + const response = await fetch('https://agilite.slm-lab.net/api/jsonrpc', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${process.env.LEANTIME_TOKEN}`, + }, + body: JSON.stringify({ + method: 'leantime.rpc.Users.Users.addUser', + jsonrpc: '2.0', + id: 1, + params: { + values: { + firstname: userData.firstName, + lastname: userData.lastName, + username: userData.username, + email: userData.email, + status: 'active', + role: 'user', // Default role in Leantime + } + } + }) + }); + + const data = await response.json(); + + if (!response.ok || !data.result) { + console.error('Leantime user creation failed:', data); + return { + success: false, + error: 'Failed to create user in Leantime' + }; + } + + return { success: true }; + } catch (error) { + console.error('Error creating Leantime user:', error); + return { + success: false, + error: 'Error creating user in Leantime' + }; + } +} + export async function POST(req: Request) { const session = await getServerSession(authOptions); @@ -257,7 +308,7 @@ export async function POST(req: Request) { ); } - // Create the user + // Create the user in Keycloak const createResponse = await fetch( `${process.env.KEYCLOAK_BASE_URL}/admin/realms/${process.env.KEYCLOAK_REALM}/users`, { @@ -356,6 +407,20 @@ export async function POST(req: Request) { ); } + // Create user in Leantime + const leantimeResult = await createLeantimeUser({ + username: data.username, + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + }); + + if (!leantimeResult.success) { + console.error("Leantime user creation failed:", leantimeResult.error); + // We don't return an error here since Keycloak user was created successfully + // We just log the error and continue + } + return NextResponse.json({ success: true, user: { diff --git a/keycloak-user-creation-workflow.json b/keycloak-user-creation-workflow.json new file mode 100644 index 00000000..d6a9198c --- /dev/null +++ b/keycloak-user-creation-workflow.json @@ -0,0 +1,338 @@ +{ + "name": "Keycloak User Creation", + "nodes": [ + { + "parameters": { + "path": "create-user", + "options": { + "responseMode": "lastNode", + "responseData": "allEntries" + } + }, + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 100, + 300 + ], + "webhookId": "create-user-webhook" + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/admin/realms/cercle/users", + "options": {}, + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "nodeCredentialType": "httpHeaderAuth", + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "={{$node[\"Get Admin Token\"].json[\"access_token\"]}}" + } + ] + } + }, + "name": "HTTP Request", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 500, + 300 + ] + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/realms/cercle/protocol/openid-connect/token", + "options": {}, + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "nodeCredentialType": "httpHeaderAuth", + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "bodyParameters": { + "parameters": [ + { + "name": "grant_type", + "value": "client_credentials" + }, + { + "name": "client_id", + "value": "lab" + }, + { + "name": "client_secret", + "value": "LwgeE1ntADD20OuWC88S3pR0EaO7FtO4" + } + ] + } + }, + "name": "Get Admin Token", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 300, + 300 + ] + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{$json[\"isValid\"]}}", + "value2": "false" + } + ] + } + }, + "name": "IF", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 300, + 500 + ] + }, + { + "parameters": { + "functionCode": "// Validate username according to Keycloak requirements\nconst username = $input.item.json.username;\n\n// Keycloak username requirements:\n// - Only alphanumeric characters, dots (.), hyphens (-), and underscores (_)\n// - Must start with a letter or number\n// - Must be between 3 and 255 characters\nconst usernameRegex = /^[a-zA-Z0-9][a-zA-Z0-9._-]{2,254}$/;\n\nif (!usernameRegex.test(username)) {\n return {\n isValid: false,\n error: \"Le nom d'utilisateur doit commencer par une lettre ou un chiffre, ne contenir que des lettres, chiffres, points, tirets et underscores, et faire entre 3 et 255 caractères\"\n };\n}\n\nreturn { isValid: true };" + }, + "name": "Validate Username", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [ + 300, + 400 + ] + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/admin/realms/cercle/roles", + "options": {} + }, + "name": "Get Available Roles", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 500, + 400 + ] + }, + { + "parameters": { + "functionCode": "// Filter valid roles\nconst requestedRoles = $input.item.json.roles || [];\nconst availableRoles = $input.item.json.roles;\n\nconst validRoles = requestedRoles.filter(roleName => \n availableRoles.some(r => r.name === roleName)\n);\n\nif (validRoles.length === 0) {\n return {\n isValid: false,\n error: \"Aucun rôle valide n'a été spécifié\"\n };\n}\n\nreturn {\n isValid: true,\n validRoles: validRoles,\n roleObjects: validRoles.map(roleName => \n availableRoles.find(r => r.name === roleName)\n )\n};" + }, + "name": "Validate Roles", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [ + 700, + 400 + ] + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/admin/realms/cercle/users", + "options": {}, + "body": { + "username": "={{$input.item.json.username}}", + "enabled": true, + "emailVerified": true, + "firstName": "={{$input.item.json.firstName}}", + "lastName": "={{$input.item.json.lastName}}", + "email": "={{$input.item.json.email}}", + "credentials": [ + { + "type": "password", + "value": "={{$input.item.json.password}}", + "temporary": false + } + ] + } + }, + "name": "Create User", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 900, + 400 + ] + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/admin/realms/cercle/users?username={{$input.item.json.username}}", + "options": {} + }, + "name": "Get Created User", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 1100, + 400 + ] + }, + { + "parameters": { + "url": "https://connect.slm-lab.net/admin/realms/cercle/users/{{$input.item.json[0].id}}/role-mappings/realm", + "options": {}, + "body": "={{$input.item.json.roleObjects}}" + }, + "name": "Assign Roles", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 3, + "position": [ + 1300, + 400 + ] + }, + { + "parameters": { + "functionCode": "// Format success response\nreturn {\n success: true,\n user: {\n ...$input.item.json[0],\n roles: $input.item.json.validRoles\n }\n};" + }, + "name": "Format Response", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [ + 1500, + 400 + ] + }, + { + "parameters": { + "functionCode": "// Format error response\nreturn {\n success: false,\n error: $input.item.json.error\n};" + }, + "name": "Format Error", + "type": "n8n-nodes-base.function", + "typeVersion": 1, + "position": [ + 500, + 600 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Get Admin Token", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Admin Token": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + }, + { + "node": "Validate Username", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validate Username": { + "main": [ + [ + { + "node": "IF", + "type": "main", + "index": 0 + } + ] + ] + }, + "IF": { + "main": [ + [ + { + "node": "Format Error", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Get Available Roles", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Available Roles": { + "main": [ + [ + { + "node": "Validate Roles", + "type": "main", + "index": 0 + } + ] + ] + }, + "Validate Roles": { + "main": [ + [ + { + "node": "Create User", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create User": { + "main": [ + [ + { + "node": "Get Created User", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get Created User": { + "main": [ + [ + { + "node": "Assign Roles", + "type": "main", + "index": 0 + } + ] + ] + }, + "Assign Roles": { + "main": [ + [ + { + "node": "Format Response", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + }, + "version": 1 +} \ No newline at end of file