fix: improve GPS enrollment and simplify Auth0 SSO

- Remove dashes from device identifiers for better compatibility
- Auto-enable consent on enrollment (HR handles consent at hiring)
- Remove consent checks from location queries and UI
- Simplify Traccar Admin to use Auth0 SSO directly
- Fix server URL to return base Traccar URL (app handles port)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 18:56:16 +01:00
parent 5ded039793
commit 5a22a4dd46
4 changed files with 19 additions and 47 deletions

View File

@@ -111,7 +111,6 @@ export class GpsService implements OnModuleInit {
const activeDrivers = await this.prisma.gpsDevice.count({ const activeDrivers = await this.prisma.gpsDevice.count({
where: { where: {
isActive: true, isActive: true,
consentGiven: true,
lastActive: { lastActive: {
gte: new Date(Date.now() - 5 * 60 * 1000), // Active in last 5 minutes gte: new Date(Date.now() - 5 * 60 * 1000), // Active in last 5 minutes
}, },
@@ -142,7 +141,6 @@ export class GpsService implements OnModuleInit {
success: boolean; success: boolean;
deviceIdentifier: string; deviceIdentifier: string;
serverUrl: string; serverUrl: string;
port: number;
instructions: string; instructions: string;
signalMessageSent?: boolean; signalMessageSent?: boolean;
}> { }> {
@@ -164,8 +162,8 @@ export class GpsService implements OnModuleInit {
throw new BadRequestException('Driver is already enrolled for GPS tracking'); throw new BadRequestException('Driver is already enrolled for GPS tracking');
} }
// Generate unique device identifier // Generate unique device identifier (no special characters for better compatibility)
const deviceIdentifier = `vip-driver-${driverId.slice(0, 8)}`; const deviceIdentifier = `vipdriver${driverId.replace(/-/g, '').slice(0, 8)}`;
// Create device in Traccar // Create device in Traccar
const traccarDevice = await this.traccarClient.createDevice( const traccarDevice = await this.traccarClient.createDevice(
@@ -174,13 +172,14 @@ export class GpsService implements OnModuleInit {
driver.phone || undefined, driver.phone || undefined,
); );
// Create GPS device record // Create GPS device record (consent pre-approved by HR at hiring)
await this.prisma.gpsDevice.create({ await this.prisma.gpsDevice.create({
data: { data: {
driverId, driverId,
traccarDeviceId: traccarDevice.id, traccarDeviceId: traccarDevice.id,
deviceIdentifier, deviceIdentifier,
consentGiven: false, consentGiven: true,
consentGivenAt: new Date(),
}, },
}); });
@@ -229,7 +228,6 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
success: true, success: true,
deviceIdentifier, deviceIdentifier,
serverUrl, serverUrl,
port: 5055,
instructions, instructions,
signalMessageSent, signalMessageSent,
}; };
@@ -316,7 +314,6 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
const devices = await this.prisma.gpsDevice.findMany({ const devices = await this.prisma.gpsDevice.findMany({
where: { where: {
isActive: true, isActive: true,
consentGiven: true,
driver: { driver: {
deletedAt: null, deletedAt: null,
}, },
@@ -547,7 +544,6 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
const devices = await this.prisma.gpsDevice.findMany({ const devices = await this.prisma.gpsDevice.findMany({
where: { where: {
isActive: true, isActive: true,
consentGiven: true,
}, },
}); });
@@ -689,7 +685,8 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
} }
/** /**
* Get auto-login URL for Traccar (for admin users) * Get Traccar admin URL (Auth0 SSO handles authentication)
* User's Auth0 role determines admin status in Traccar
*/ */
async getTraccarAutoLoginUrl(user: User): Promise<{ async getTraccarAutoLoginUrl(user: User): Promise<{
url: string; url: string;
@@ -699,36 +696,29 @@ Note: GPS tracking is only active during shift hours (${settings.shiftStartHour}
throw new BadRequestException('Only administrators can access Traccar admin'); throw new BadRequestException('Only administrators can access Traccar admin');
} }
// Ensure user is synced to Traccar (this also sets up their token) // Just return the Traccar URL - Auth0 SSO handles authentication
await this.syncUserToTraccar(user); // User must have ADMINISTRATOR role in Auth0 to get admin access in Traccar
// Get the token for auto-login
const token = this.generateTraccarToken(user.id);
const baseUrl = this.traccarClient.getTraccarUrl(); const baseUrl = this.traccarClient.getTraccarUrl();
// Return URL with token parameter for auto-login
// Traccar supports ?token=xxx for direct authentication
return { return {
url: `${baseUrl}?token=${token}`, url: baseUrl,
directAccess: true, directAccess: true,
}; };
} }
/** /**
* Get Traccar session cookie for a user (for proxy/iframe auth) * Get Traccar session cookie for a user (for proxy/iframe auth)
* Note: With Auth0 SSO (openid.force=true), this won't work.
* Use getTraccarAutoLoginUrl() instead for direct redirect.
*/ */
async getTraccarSessionForUser(user: User): Promise<string | null> { async getTraccarSessionForUser(user: User): Promise<string | null> {
if (user.role !== 'ADMINISTRATOR') { if (user.role !== 'ADMINISTRATOR') {
return null; return null;
} }
// Ensure user is synced // With Auth0 SSO, session creation via password is disabled
await this.syncUserToTraccar(user); // Return null to indicate direct access via URL is needed
return null;
const password = this.generateTraccarPassword(user.id);
const session = await this.traccarClient.createUserSession(user.email, password);
return session?.cookie || null;
} }
/** /**

View File

@@ -342,21 +342,12 @@ export class TraccarClientService implements OnModuleInit {
} }
/** /**
* Get the device port URL for mobile app configuration * Get the device server URL for mobile app configuration
* Returns full HTTPS URL - nginx terminates SSL on port 5055 * Returns the Traccar URL (nginx handles SSL termination and proxies to Traccar)
*/ */
getDeviceServerUrl(): string { getDeviceServerUrl(): string {
const port = this.configService.get<string>('TRACCAR_DEVICE_PORT') || '5055'; // Return the base Traccar URL - nginx handles routing OsmAnd protocol
return this.getTraccarUrl();
// Use the Traccar public URL to derive the device server hostname
const traccarUrl = this.getTraccarUrl();
try {
const url = new URL(traccarUrl);
// Return HTTPS URL - nginx terminates SSL and proxies to Traccar
return `https://${url.hostname}:${port}`;
} catch {
return `http://localhost:${port}`;
}
} }
// ============================================ // ============================================

View File

@@ -462,7 +462,6 @@ export function GpsTracking() {
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Driver</th> <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Driver</th>
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Device ID</th> <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Device ID</th>
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Status</th> <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Status</th>
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Consent</th>
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Last Active</th> <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Last Active</th>
<th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th> <th className="px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase">Actions</th>
</tr> </tr>
@@ -484,13 +483,6 @@ export function GpsTracking() {
{device.isActive ? 'Active' : 'Inactive'} {device.isActive ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td className="px-4 py-3">
{device.consentGiven ? (
<CheckCircle className="h-5 w-5 text-green-600" />
) : (
<XCircle className="h-5 w-5 text-gray-400" />
)}
</td>
<td className="px-4 py-3 text-sm text-muted-foreground"> <td className="px-4 py-3 text-sm text-muted-foreground">
{device.lastActive ? formatDistanceToNow(new Date(device.lastActive), { addSuffix: true }) : 'Never'} {device.lastActive ? formatDistanceToNow(new Date(device.lastActive), { addSuffix: true }) : 'Never'}
</td> </td>

View File

@@ -83,7 +83,6 @@ export interface EnrollmentResponse {
success: boolean; success: boolean;
deviceIdentifier: string; deviceIdentifier: string;
serverUrl: string; serverUrl: string;
port: number;
instructions: string; instructions: string;
signalMessageSent?: boolean; signalMessageSent?: boolean;
} }