Commit 7fa82e6d by Mac Stephens

Fix WC device management, SMS verification handling, and map marker loading

parent 13ffaf77
......@@ -28,3 +28,15 @@ emiMobileServer/Source/__recovery/
*.dll
webEMIMobile/config/__history/
emiMobileServer/Clients/BFD/Web/__history/
emiMobileServer/Clients/WC/Web/__history/
emiMobileServer/Shared/Server/__history/
emiMobileServer/Clients/WC/Web/css/__history/
emiMobileServer/Clients/BFD/Web/Win32/Debug/
emiMobileServer/Clients/WC/Web/Win32/Debug/
......@@ -39,8 +39,7 @@ type
function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function DeleteDevice(DeviceId: Integer): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject;
function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject;
end;
......
......@@ -45,8 +45,7 @@ type
function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function DeleteDevice(DeviceId: Integer): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject;
function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject;
end;
......@@ -1626,6 +1625,21 @@ begin
try
q.Connection := conn;
// Note: Device names are unique regardless of capitalization or surrounding spaces.
q.SQL.Text :=
'SELECT id FROM lems.device_registrations ' +
'WHERE UPPER(TRIM(device_name)) = UPPER(:NAME)';
q.ParamByName('NAME').AsString := Trim(DeviceName);
q.Open;
if not q.IsEmpty then
begin
q.Close;
Result.AddPair('status', 'error');
Result.AddPair('message', 'A device with that name already exists. Enter a different device name.');
Exit;
end;
q.Close;
// Reject if a pending entry with this phone already exists
q.SQL.Text :=
'SELECT id FROM lems.device_registrations ' +
......@@ -1661,59 +1675,7 @@ begin
end;
end;
function TApiService.DeletePendingDevice(const PhoneNumber: string): TJSONObject;
var
conn: TUniConnection;
q: TUniQuery;
normalizedPhone: string;
begin
RequireAdmin;
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
try
normalizedPhone := NormalizePhoneE164Api(PhoneNumber);
except
on E: Exception do
begin
Result.AddPair('status', 'error');
Result.AddPair('message', E.Message);
Exit;
end;
end;
conn := OpenLemsConnection;
try
q := TUniQuery.Create(nil);
try
q.Connection := conn;
q.SQL.Text :=
'DELETE FROM lems.device_registrations ' +
'WHERE phone_number = :PHONE AND status = ''pending''';
q.ParamByName('PHONE').AsString := normalizedPhone;
q.ExecSQL;
if q.RowsAffected > 0 then
begin
Logger.Log(2, 'TApiService.DeletePendingDevice - deleted phone: ' + normalizedPhone);
Result.AddPair('status', 'ok');
Result.AddPair('message', 'Pending device removed.');
end
else
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'Pending device not found.');
end;
finally
q.Free;
end;
finally
conn.Free;
end;
end;
function TApiService.DeleteDevice(CredentialId: string): TJSONObject;
function TApiService.DeleteDevice(DeviceId: Integer): TJSONObject;
var
conn: TUniConnection;
q: TUniQuery;
......@@ -1723,10 +1685,10 @@ begin
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
if Trim(CredentialId) = '' then
if DeviceId <= 0 then
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'CredentialId is required.');
Result.AddPair('message', 'DeviceId is required.');
Exit;
end;
......@@ -1737,14 +1699,13 @@ begin
q.Connection := conn;
q.SQL.Text :=
'DELETE FROM lems.device_registrations ' +
'WHERE credential_id = :CID';
q.ParamByName('CID').AsString := Trim(CredentialId);
'WHERE id = :ID';
q.ParamByName('ID').AsInteger := DeviceId;
q.ExecSQL;
if q.RowsAffected > 0 then
begin
Logger.Log(2, 'TApiService.DeleteDevice - deleted credential: ' +
Copy(CredentialId, 1, 20));
Logger.Log(2, 'TApiService.DeleteDevice - deleted id: ' + IntToStr(DeviceId));
Result.AddPair('status', 'ok');
Result.AddPair('message', 'Device deleted.');
end
......
......@@ -29,7 +29,7 @@ type
function LoadUserByName(const User, Agency: string): Boolean;
function Decrypt(inStr, keyStr: AnsiString): AnsiString;
function GenerateVerificationCode: string;
procedure SendVerificationText(const PhoneNumber, Code: string);
function SendVerificationText(const PhoneNumber, Code: string): Boolean;
// Returns True and sets AChallengeB64 if token is valid and of the expected type
function VerifyChallengeToken(const ChallengeToken, ExpectedType: string;
out AChallengeB64: string): Boolean;
......@@ -209,7 +209,7 @@ begin
Result := FormatFloat('000000', value mod 1000000);
end;
procedure TAuthService.SendVerificationText(const PhoneNumber, Code: string);
function TAuthService.SendVerificationText(const PhoneNumber, Code: string): Boolean;
var
httpClient: THTTPClient;
bodyStream: TStringStream;
......@@ -239,9 +239,10 @@ begin
response := httpClient.Post(
'https://api.twilio.com/2010-04-01/Accounts/' +
ServerConfig.twilioAccountSid + '/Messages.json', bodyStream);
if response.StatusCode >= 300 then
raise Exception.CreateFmt('Twilio error %d: %s',
[response.StatusCode, response.ContentAsString]);
Result := (response.StatusCode >= 200) and (response.StatusCode < 300);
if not Result then
Logger.Log(2, Format('Twilio rejected verification SMS, HTTP %d: %s',
[response.StatusCode, response.ContentAsString]));
finally
bodyStream.Free;
end;
......@@ -308,6 +309,24 @@ begin
end;
code := GenerateVerificationCode;
try
if not SendVerificationText(normalizedPhone, code) then
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'The SMS provider rejected the verification message. Contact your administrator to check the phone number and SMS configuration.');
Exit;
end;
except
on E: Exception do
begin
Logger.Log(2, '--Error sending registration verification code: ' + E.Message);
Result.AddPair('status', 'error');
Result.AddPair('message', 'The verification code could not be sent. Please try again.');
Exit;
end;
end;
// Note: Persist the code only after the SMS provider accepts the message.
q := TUniQuery.Create(nil);
try
......@@ -330,33 +349,6 @@ begin
q.Free;
end;
try
SendVerificationText(normalizedPhone, code);
except
on E: Exception do
begin
q := TUniQuery.Create(nil);
try
q.Connection := authDB.ucLemsOCSO;
q.SQL.Text :=
'UPDATE lems.device_registrations ' +
'SET verification_code = NULL, verification_expires_at = NULL ' +
'WHERE phone_number = :PHONE AND status = ''pending'' ' +
'AND verification_code = :CODE';
q.ParamByName('PHONE').AsString := normalizedPhone;
q.ParamByName('CODE').AsString := code;
q.ExecSQL;
finally
q.Free;
end;
Logger.Log(2, '--Error sending registration verification code: ' + E.Message);
Result.AddPair('status', 'error');
Result.AddPair('message', 'The verification code could not be sent. Please try again.');
Exit;
end;
end;
Logger.Log(3, Format(
'Verification code sent for device id %d and device name: %s',
[deviceId, deviceName]));
......
......@@ -79,12 +79,16 @@ end;
destructor TWsDataModel.Destroy;
begin
if Assigned(FListenerTimer) then
FListenerTimer.Enabled := False;
FListenerTimer.Free;
if Assigned(FThread) then
begin
FThread.Terminate;
FStopEvent.SetEvent;
FThread.WaitFor;
FThread.Free;
end;
FListenerDb.Free;
FStopEvent.Free;
inherited;
......
[Settings]
LogFileNum=154
LogFileNum=155
webClientVersion=0.9.4.9
MemoLogLevel=3
FileLogLevel=4
......
......@@ -81,6 +81,15 @@ object FViewDeviceManager: TFViewDeviceManager
WidthPercent = 100.000000000000000000
OnClick = btnLogoutClick
end
object btnRefresh: TWebButton
Left = 620
Top = 50
Width = 75
Height = 25
Caption = 'Refresh'
ElementID = 'view.devmgr.btnrefresh'
OnClick = btnRefreshClick
end
object XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 800
......
<div class="container-fluid p-3 h-100 d-flex flex-column">
<div class="d-flex align-items-center mb-3">
<div class="d-flex align-items-center flex-wrap gap-2 mb-3">
<h5 class="mb-0 me-auto">Device Management</h5>
<button id="view.devmgr.btnlogout" class="btn btn-outline-secondary btn-sm me-2" type="button">
Log Out
</button>
<button id="view.devmgr.btnrefresh"
class="btn btn-outline-secondary btn-sm"
onclick="document.dispatchEvent(new CustomEvent('devmgr-refresh'))">
type="button">
Refresh
</button>
</div>
......@@ -26,9 +26,9 @@
<!-- Add pending device -->
<div class="card mb-3">
<div class="card-body py-2">
<div class="d-flex align-items-start gap-2 flex-wrap">
<span class="fw-semibold text-nowrap small mt-2">Add Device:</span>
<div style="width:180px;">
<div class="row g-2 align-items-start">
<div class="col-12 col-md-auto"><span class="fw-semibold text-nowrap small">Add Device:</span></div>
<div class="col-6 col-md-auto">
<input type="text"
id="view.devmgr.newname"
class="form-control form-control-sm"
......@@ -36,7 +36,7 @@
required>
<div class="invalid-feedback small">Device name is required.</div>
</div>
<div style="width:160px;">
<div class="col-6 col-md-auto">
<input type="tel"
id="view.devmgr.newphone"
class="form-control form-control-sm"
......@@ -44,7 +44,7 @@
required>
<div class="invalid-feedback small">Phone number is required.</div>
</div>
<div style="width:140px;">
<div class="col-6 col-md-auto">
<select id="view.devmgr.newtype"
class="form-select form-select-sm"
required>
......@@ -56,20 +56,24 @@
</select>
<div class="invalid-feedback small">Device type is required.</div>
</div>
<button id="view.devmgr.btnadd"
class="btn btn-primary btn-sm">Add</button>
<div class="col-6 col-md-auto d-grid">
<button id="view.devmgr.btnadd" class="btn btn-primary btn-sm">Add</button>
</div>
</div>
</div>
</div>
<!-- Device table -->
<div class="table-responsive flex-grow-1">
<div class="flex-grow-1 overflow-auto">
<div id="view.devmgr.cards" class="d-md-none"></div>
<div class="table-responsive d-none d-md-block">
<table class="table table-sm table-hover align-middle" id="view.devmgr.table">
<thead class="table-light sticky-top">
<tr>
<th style="min-width:130px;">Device Name</th>
<th style="min-width:100px;">Device Type</th>
<th style="min-width:120px;">Phone</th>
<th style="min-width:145px;">Verification Code</th>
<th style="min-width:110px;">Verification Code</th>
<th style="min-width:140px;">Username</th>
<th>Browser / User Agent</th>
<th style="min-width:135px;">Registered</th>
......@@ -82,6 +86,7 @@
<tbody id="view.devmgr.tbody">
</tbody>
</table>
</div>
<p id="view.devmgr.empty" class="text-muted d-none text-center py-4">
No devices found.
</p>
......
......@@ -98,6 +98,8 @@ end;
procedure TFViewMap.UpdateDeviceLocation(lat, lng: Double);
begin
lfMap.BeginUpdate;
try
if userLocationMarker = nil then
begin
userLocationMarker := lfMap.Markers.Add;
......@@ -109,6 +111,9 @@ begin
userLocationMarker.Latitude := lat;
userLocationMarker.Longitude := lng;
finally
lfMap.EndUpdate;
end;
end;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment