#!/usr/bin/env node /** * Auth0 Setup Script for Traccar GPS Integration * * This script sets up Auth0 roles and actions needed for Traccar GPS tracking * to work with OpenID Connect authentication. * * Usage: * node setup-auth0-traccar.js --token= --domain= --traccar-url= --admins= * * Or with environment variables: * AUTH0_MANAGEMENT_TOKEN= \ * AUTH0_DOMAIN= \ * TRACCAR_URL= \ * ADMIN_EMAILS= \ * node setup-auth0-traccar.js * * Examples: * node setup-auth0-traccar.js \ * --token=eyJ... \ * --domain=my-tenant.us.auth0.com \ * --traccar-url=https://traccar.myapp.com \ * --admins=admin@example.com,backup-admin@example.com * * What this script does: * 1. Creates ADMINISTRATOR and COORDINATOR roles in Auth0 * 2. Creates a Post Login Action that adds roles to tokens as "groups" * 3. Deploys the action to the Login flow * 4. Assigns ADMINISTRATOR role to specified users (if they exist in Auth0) * * After running this script: * - Users with ADMINISTRATOR role get admin access to Traccar * - Users with COORDINATOR role get standard access to Traccar * - Users without either role cannot access Traccar * - Manage roles in Auth0 Dashboard → User Management → Users → [user] → Roles */ // Parse command line arguments function parseArgs() { const args = {}; process.argv.slice(2).forEach(arg => { if (arg.startsWith('--')) { const [key, value] = arg.slice(2).split('='); args[key] = value; } }); return args; } const args = parseArgs(); // Configuration - from args, env vars, or defaults const AUTH0_DOMAIN = args.domain || process.env.AUTH0_DOMAIN || ''; const TRACCAR_URL = args['traccar-url'] || process.env.TRACCAR_URL || ''; const TRACCAR_NAMESPACE = TRACCAR_URL; // Namespace matches the Traccar URL // Users to assign ADMINISTRATOR role to (comma-separated) const ADMIN_EMAILS = (args.admins || process.env.ADMIN_EMAILS || '') .split(',') .map(e => e.trim()) .filter(e => e.length > 0); async function main() { const token = args.token || process.env.AUTH0_MANAGEMENT_TOKEN; // Validate required parameters const missing = []; if (!token) missing.push('--token or AUTH0_MANAGEMENT_TOKEN'); if (!AUTH0_DOMAIN) missing.push('--domain or AUTH0_DOMAIN'); if (!TRACCAR_URL) missing.push('--traccar-url or TRACCAR_URL'); if (ADMIN_EMAILS.length === 0) missing.push('--admins or ADMIN_EMAILS'); if (missing.length > 0) { console.error('Missing required parameters:'); missing.forEach(m => console.error(` - ${m}`)); console.error('\nUsage:'); console.error(' node setup-auth0-traccar.js \\'); console.error(' --token= \\'); console.error(' --domain= \\'); console.error(' --traccar-url= \\'); console.error(' --admins='); console.error('\nExample:'); console.error(' node setup-auth0-traccar.js \\'); console.error(' --token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... \\'); console.error(' --domain=my-company.us.auth0.com \\'); console.error(' --traccar-url=https://traccar.myapp.com \\'); console.error(' --admins=john@company.com,jane@company.com'); process.exit(1); } const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; console.log('=== Auth0 Traccar Setup ===\n'); console.log(`Auth0 Domain: ${AUTH0_DOMAIN}`); console.log(`Traccar URL: ${TRACCAR_URL}`); console.log(`Admin Emails: ${ADMIN_EMAILS.join(', ')}\n`); // Step 1: Create roles console.log('1. Creating roles...'); const adminRole = await createRole(headers, { name: 'ADMINISTRATOR', description: 'Full admin access to VIP Coordinator and Traccar GPS tracking' }); console.log(` - ADMINISTRATOR role: ${adminRole ? 'created' : 'already exists'}`); const coordRole = await createRole(headers, { name: 'COORDINATOR', description: 'Coordinator access to VIP Coordinator and Traccar GPS tracking' }); console.log(` - COORDINATOR role: ${coordRole ? 'created' : 'already exists'}`); // Get role IDs const roles = await getRoles(headers); const adminRoleId = roles.find(r => r.name === 'ADMINISTRATOR')?.id; const coordRoleId = roles.find(r => r.name === 'COORDINATOR')?.id; console.log(` Admin Role ID: ${adminRoleId}`); console.log(` Coordinator Role ID: ${coordRoleId}`); // Step 2: Create the Post Login Action console.log('\n2. Creating Post Login Action...'); const actionCode = ` exports.onExecutePostLogin = async (event, api) => { const namespace = '${TRACCAR_NAMESPACE}'; if (event.authorization && event.authorization.roles) { // Add roles as "groups" claim (what Traccar expects) api.idToken.setCustomClaim(namespace + '/groups', event.authorization.roles); api.accessToken.setCustomClaim(namespace + '/groups', event.authorization.roles); } }; `; const action = await createOrUpdateAction(headers, { name: 'Add Roles to Traccar Groups', code: actionCode.trim(), supported_triggers: [{ id: 'post-login', version: 'v3' }], runtime: 'node18' }); if (action) { console.log(` - Action created: ${action.id}`); // Deploy the action console.log('\n3. Deploying action...'); await deployAction(headers, action.id); console.log(' - Action deployed'); // Add to login flow console.log('\n4. Adding action to Login flow...'); await addActionToFlow(headers, action.id); console.log(' - Action added to flow'); } else { console.log(' - Action already exists or failed to create'); } // Step 3: Assign ADMINISTRATOR role to admin users console.log('\n5. Assigning ADMINISTRATOR role to admin users...'); for (const email of ADMIN_EMAILS) { const user = await findUserByEmail(headers, email); if (user) { await assignRoleToUser(headers, user.user_id, adminRoleId); console.log(` - ${email}: ADMINISTRATOR role assigned`); } else { console.log(` - ${email}: User not found (will get role on first login)`); } } console.log('\n=== Setup Complete ==='); console.log('\nNext steps:'); console.log('1. Update Traccar config with these settings:'); console.log(` ${TRACCAR_NAMESPACE}/groups`); console.log(' ADMINISTRATOR'); console.log(' ADMINISTRATOR,COORDINATOR'); console.log('\n2. Restart Traccar container'); console.log('\n3. Test login with an admin user'); } async function createRole(headers, roleData) { try { const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/roles`, { method: 'POST', headers, body: JSON.stringify(roleData) }); if (response.status === 409) { // Role already exists return null; } if (!response.ok) { const error = await response.text(); console.error(`Failed to create role ${roleData.name}:`, error); return null; } return await response.json(); } catch (error) { console.error(`Error creating role ${roleData.name}:`, error.message); return null; } } async function getRoles(headers) { try { const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/roles`, { method: 'GET', headers }); if (!response.ok) { console.error('Failed to get roles'); return []; } return await response.json(); } catch (error) { console.error('Error getting roles:', error.message); return []; } } async function createOrUpdateAction(headers, actionData) { try { // Check if action exists const listResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions?actionName=${encodeURIComponent(actionData.name)}`, { method: 'GET', headers }); if (listResponse.ok) { const actions = await listResponse.json(); const existing = actions.actions?.find(a => a.name === actionData.name); if (existing) { // Update existing action const updateResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions/${existing.id}`, { method: 'PATCH', headers, body: JSON.stringify({ code: actionData.code }) }); if (updateResponse.ok) { return await updateResponse.json(); } } } // Create new action const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions`, { method: 'POST', headers, body: JSON.stringify(actionData) }); if (!response.ok) { const error = await response.text(); console.error('Failed to create action:', error); return null; } return await response.json(); } catch (error) { console.error('Error creating action:', error.message); return null; } } async function deployAction(headers, actionId) { try { const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/actions/${actionId}/deploy`, { method: 'POST', headers }); if (!response.ok) { const error = await response.text(); console.error('Failed to deploy action:', error); return false; } return true; } catch (error) { console.error('Error deploying action:', error.message); return false; } } async function addActionToFlow(headers, actionId) { try { // Get current flow bindings const getResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/triggers/post-login/bindings`, { method: 'GET', headers }); if (!getResponse.ok) { console.error('Failed to get flow bindings'); return false; } const currentBindings = await getResponse.json(); // Check if action is already in flow const alreadyBound = currentBindings.bindings?.some(b => b.action?.id === actionId); if (alreadyBound) { console.log(' - Action already in flow'); return true; } // Add action to flow const newBindings = [ ...(currentBindings.bindings || []).map(b => ({ ref: { type: 'action_id', value: b.action.id } })), { ref: { type: 'action_id', value: actionId } } ]; const updateResponse = await fetch(`https://${AUTH0_DOMAIN}/api/v2/actions/triggers/post-login/bindings`, { method: 'PATCH', headers, body: JSON.stringify({ bindings: newBindings }) }); if (!updateResponse.ok) { const error = await updateResponse.text(); console.error('Failed to update flow bindings:', error); return false; } return true; } catch (error) { console.error('Error adding action to flow:', error.message); return false; } } async function findUserByEmail(headers, email) { try { const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/users-by-email?email=${encodeURIComponent(email)}`, { method: 'GET', headers }); if (!response.ok) { return null; } const users = await response.json(); return users[0] || null; } catch (error) { console.error(`Error finding user ${email}:`, error.message); return null; } } async function assignRoleToUser(headers, userId, roleId) { try { const response = await fetch(`https://${AUTH0_DOMAIN}/api/v2/users/${encodeURIComponent(userId)}/roles`, { method: 'POST', headers, body: JSON.stringify({ roles: [roleId] }) }); if (!response.ok && response.status !== 204) { const error = await response.text(); console.error(`Failed to assign role to user:`, error); return false; } return true; } catch (error) { console.error('Error assigning role:', error.message); return false; } } main().catch(console.error);