Commit 97fe9d07 by Mac Stephens

Clean up retired code, finish BFD parity fixes, and support registration by phone and device type

parent 445d8738
...@@ -383,6 +383,9 @@ begin ...@@ -383,6 +383,9 @@ begin
DataSet.FieldByName('complaintStatusKey').AsString := statusKey; DataSet.FieldByName('complaintStatusKey').AsString := statusKey;
// Note: Held-for-unit incidents use the existing hold marker images.
if statusKey = 'holdu' then statusKey := 'hold';
if Trim(rawCategory) = '' then if Trim(rawCategory) = '' then
DataSet.FieldByName('pngName').AsString := Format('default_%s.png', [statusKey]) DataSet.FieldByName('pngName').AsString := Format('default_%s.png', [statusKey])
else else
...@@ -465,8 +468,15 @@ begin ...@@ -465,8 +468,15 @@ begin
end; end;
procedure TBFDAPIDataModule.ConfigureQueryTimeouts; procedure TBFDAPIDataModule.ConfigureQueryTimeouts;
const
COMMAND_TIMEOUT_SECONDS = 15;
begin begin
// Note: Preserve the existing Oracle query timeout settings. uqBadgeCounts.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
uqMapUnits.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
uqMapComplaintUnitsList.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
uqMapComplaints.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
uqUnitList.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
uqComplaintList.SpecificOptions.Values['CommandTimeout'] := IntToStr(COMMAND_TIMEOUT_SECONDS);
end; end;
function TBFDAPIDataModule.GetBadgeCounts: TJSONObject; function TBFDAPIDataModule.GetBadgeCounts: TJSONObject;
......
...@@ -4,6 +4,13 @@ object BFDAuthDatabase: TBFDAuthDatabase ...@@ -4,6 +4,13 @@ object BFDAuthDatabase: TBFDAuthDatabase
Height = 373 Height = 373
Width = 649 Width = 649
PixelsPerInch = 144 PixelsPerInch = 144
object uqNextAuditId: TUniQuery
Connection = ucENTCAD
SQL.Strings = (
'SELECT SEQ_AUDIT_MASTERID.NEXTVAL AS NEXTSEQVAL FROM DUAL')
Left = 450
Top = 75
end
object uqMisc: TUniQuery object uqMisc: TUniQuery
Connection = ucENTCAD Connection = ucENTCAD
FetchRows = 100 FetchRows = 100
......
...@@ -9,6 +9,7 @@ uses ...@@ -9,6 +9,7 @@ uses
type type
TBFDAuthDatabase = class(TClientAuth) TBFDAuthDatabase = class(TClientAuth)
uqMisc: TUniQuery; uqMisc: TUniQuery;
uqNextAuditId: TUniQuery;
uqAuth: TUniQuery; uqAuth: TUniQuery;
uqAgencies: TUniQuery; uqAgencies: TUniQuery;
uqAgencyConfig: TUniQuery; uqAgencyConfig: TUniQuery;
...@@ -139,7 +140,12 @@ begin ...@@ -139,7 +140,12 @@ begin
if ServerConfig.auditEnabled then if ServerConfig.auditEnabled then
begin begin
auditMasterId := GetNextSeqVal(uqMisc, 'SEQ_AUDIT_MASTERID'); uqNextAuditId.Open;
try
auditMasterId := uqNextAuditId.FieldByName('NEXTSEQVAL').AsString;
finally
uqNextAuditId.Close;
end;
SetMasterAuditEntry(uqMisc, auditMasterId, 'AUTH', '', agency, personnelid, fullname, 'Login', '', 'emiMobile'); SetMasterAuditEntry(uqMisc, auditMasterId, 'AUTH', '', agency, personnelid, fullname, 'Login', '', 'emiMobile');
end end
else else
......
...@@ -51,13 +51,13 @@ type ...@@ -51,13 +51,13 @@ type
procedure ClearCredentialId; procedure ClearCredentialId;
// Section: Device registration verification // Section: Device registration verification
procedure BeginRegistration(APhoneNumber: string; procedure BeginRegistration(APhoneNumber, ADeviceType: string;
ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError); ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError);
procedure VerifyRegistrationCode(APhoneNumber, AVerificationCode: string; procedure VerifyRegistrationCode(APhoneNumber, ADeviceType, AVerificationCode: string;
ASuccess: TOnBeginSuccess; ASuccess: TOnBeginSuccess;
AError: TOnDeviceError); AError: TOnDeviceError);
// Section: WebAuthn registration // Section: WebAuthn registration
procedure CompleteRegistration(APhoneNumber, ACredentialId, procedure CompleteRegistration(APhoneNumber, ADeviceType, ACredentialId,
AAttestationObject, AClientDataJSON, AChallengeToken: string; AAttestationObject, AClientDataJSON, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
...@@ -74,7 +74,7 @@ type ...@@ -74,7 +74,7 @@ type
ASuccess: TOnLoginSuccess; AError: TOnLoginError); ASuccess: TOnLoginSuccess; AError: TOnLoginError);
// Simple-key registration and login // Simple-key registration and login
procedure CompleteRegistrationSimple(APhoneNumber, ADeviceKey, AChallengeToken: string; procedure CompleteRegistrationSimple(APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
procedure LoginDeviceKey(ACredentialId, AChallengeToken: string; procedure LoginDeviceKey(ACredentialId, AChallengeToken: string;
ASuccess: TOnLoginSuccess; AError: TOnLoginError); ASuccess: TOnLoginSuccess; AError: TOnLoginError);
...@@ -218,7 +218,7 @@ end; ...@@ -218,7 +218,7 @@ end;
// ---- WebAuthn registration ---- // ---- WebAuthn registration ----
procedure TAuthService.BeginRegistration(APhoneNumber: string; procedure TAuthService.BeginRegistration(APhoneNumber, ADeviceType: string;
ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError); ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError);
procedure OnLoad(Response: TXDataClientResponse); procedure OnLoad(Response: TXDataClientResponse);
...@@ -244,12 +244,12 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string; ...@@ -244,12 +244,12 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.BeginRegistration', 'IAuthService.BeginRegistration',
[APhoneNumber], [APhoneNumber, ADeviceType],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
procedure TAuthService.VerifyRegistrationCode(APhoneNumber, procedure TAuthService.VerifyRegistrationCode(APhoneNumber, ADeviceType,
AVerificationCode: string; ASuccess: TOnBeginSuccess; AVerificationCode: string; ASuccess: TOnBeginSuccess;
AError: TOnDeviceError); AError: TOnDeviceError);
...@@ -278,12 +278,12 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber, ...@@ -278,12 +278,12 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber,
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.VerifyRegistrationCode', 'IAuthService.VerifyRegistrationCode',
[APhoneNumber, AVerificationCode], [APhoneNumber, ADeviceType, AVerificationCode],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId, procedure TAuthService.CompleteRegistration(APhoneNumber, ADeviceType, ACredentialId,
AAttestationObject, AClientDataJSON, AChallengeToken: string; AAttestationObject, AClientDataJSON, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
...@@ -317,7 +317,7 @@ procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId, ...@@ -317,7 +317,7 @@ procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId,
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.CompleteRegistration', 'IAuthService.CompleteRegistration',
[APhoneNumber, ACredentialId, AAttestationObject, AClientDataJSON, AChallengeToken], [APhoneNumber, ADeviceType, ACredentialId, AAttestationObject, AClientDataJSON, AChallengeToken],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
...@@ -431,7 +431,7 @@ end; ...@@ -431,7 +431,7 @@ end;
// ---- Simple-key registration and login ---- // ---- Simple-key registration and login ----
procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, AChallengeToken: string; procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
procedure OnLoad(Response: TXDataClientResponse); procedure OnLoad(Response: TXDataClientResponse);
...@@ -462,7 +462,7 @@ procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, ACha ...@@ -462,7 +462,7 @@ procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, ACha
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.CompleteRegistrationSimple', 'IAuthService.CompleteRegistrationSimple',
[APhoneNumber, ADeviceKey, AChallengeToken], [APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
......
<div class="container"> <div class="container">
<br /> <div class="row justify-content-center mt-5">
<div class="col-12 col-sm-10 col-md-8 col-lg-6">
<div class="card shadow-sm border-danger">
<div class="panel panel-red"> <div id="view.errorpage.title"
<div id="view.errorpage.title" class="panel-heading"> class="card-header bg-danger text-white fw-semibold">
Error Page Error
</div> </div>
<div id="view.errorpage.message" class="panel-body"> <div class="card-body">
<div id="view.errorpage.message"
class="text-danger">
Message Message
</div>
</div> </div>
<div class="panel-footer"> <div class="card-footer bg-light text-end">
<a href=".">Reload web application</a> <a href="."
class="btn btn-outline-danger btn-sm">
Reload web application
</a>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
<div class="d-flex flex-column vh-100"> <div class="d-flex flex-column vh-100 emimobile-main">
<!-- Top Nav --> <!-- Top Nav -->
<nav id="top_nav" class="navbar navbar-light bg-primary border-light text-light py-2 flex-shrink-0"> <nav id="top_nav" class="navbar navbar-light bg-primary border-light text-light py-2 flex-shrink-0">
......
...@@ -40,6 +40,7 @@ type ...@@ -40,6 +40,7 @@ type
FPendingFocusCoord: TTMSFNCMapsCoordinateRec; FPendingFocusCoord: TTMSFNCMapsCoordinateRec;
FPendingFocusZoom: Integer; FPendingFocusZoom: Integer;
FDoFocusZoom: Boolean; FDoFocusZoom: Boolean;
FPendingFocusMarkerData: string;
[async] procedure LoadPointsAsync(showBusy: Boolean); [async] procedure LoadPointsAsync(showBusy: Boolean);
procedure PlaceUnitMarkers(aData: TJSArray); procedure PlaceUnitMarkers(aData: TJSArray);
...@@ -606,15 +607,47 @@ begin ...@@ -606,15 +607,47 @@ begin
end; end;
procedure TFViewMap.tmrLocateTimer(Sender: TObject); procedure TFViewMap.tmrLocateTimer(Sender: TObject);
var
i: Integer;
markerFound: Boolean;
missingTarget: string;
begin begin
tmrLocate.Enabled := False; tmrLocate.Enabled := False;
if not FDoFocusZoom then if not FDoFocusZoom then
Exit; Exit;
if FPendingFocusMarkerData <> '' then
begin
markerFound := False;
for i := 0 to lfMap.Markers.Count - 1 do
if SameText(lfMap.Markers[i].DataString, FPendingFocusMarkerData) or
StartsText(FPendingFocusMarkerData + '|', lfMap.Markers[i].DataString) then
begin
markerFound := True;
Break;
end;
if not markerFound then
begin
missingTarget := FPendingFocusMarkerData;
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
if StartsText('unit|', missingTarget) then
ShowInformationModal('No Longer Available',
'This unit is no longer available on the map.')
else if StartsText('complaint|', missingTarget) then
ShowInformationModal('No Longer Available',
'This incident is no longer active on the map.');
Exit;
end;
end;
lfMap.SetCenterCoordinate(FPendingFocusCoord); lfMap.SetCenterCoordinate(FPendingFocusCoord);
lfMap.SetZoomLevel(FPendingFocusZoom); lfMap.SetZoomLevel(FPendingFocusZoom);
FPendingFocusMarkerData := '';
FDoFocusZoom := False; FDoFocusZoom := False;
end; end;
...@@ -622,6 +655,12 @@ procedure TFViewMap.btnFindLocationClick(Sender: TObject); ...@@ -622,6 +655,12 @@ procedure TFViewMap.btnFindLocationClick(Sender: TObject);
var var
coord: TTMSFNCMapsCoordinateRec; coord: TTMSFNCMapsCoordinateRec;
begin begin
tmrLocate.Enabled := False;
FDoFocusZoom := False;
FPendingFocusMarkerData := '';
FPendingUnitId := '';
FPendingComplaintId := '';
if userLocationMarker = nil then if userLocationMarker = nil then
Exit; Exit;
...@@ -639,6 +678,10 @@ end; ...@@ -639,6 +678,10 @@ end;
procedure TFViewMap.FocusUnit(const unitId: string); procedure TFViewMap.FocusUnit(const unitId: string);
begin begin
tmrLocate.Enabled := False;
FDoFocusZoom := False;
FPendingFocusMarkerData := '';
FPendingComplaintId := '';
FPendingUnitId := Trim(unitId); FPendingUnitId := Trim(unitId);
if mapFilters <> nil then if mapFilters <> nil then
LoadPointsAsync(True); LoadPointsAsync(True);
...@@ -647,6 +690,10 @@ end; ...@@ -647,6 +690,10 @@ end;
procedure TFViewMap.FocusComplaint(const complaintId: string); procedure TFViewMap.FocusComplaint(const complaintId: string);
begin begin
tmrLocate.Enabled := False;
FDoFocusZoom := False;
FPendingFocusMarkerData := '';
FPendingUnitId := '';
FPendingComplaintId := Trim(complaintId); FPendingComplaintId := Trim(complaintId);
if mapFilters <> nil then if mapFilters <> nil then
LoadPointsAsync(True); LoadPointsAsync(True);
...@@ -678,6 +725,7 @@ begin ...@@ -678,6 +725,7 @@ begin
FPendingFocusCoord := coord; FPendingFocusCoord := coord;
FPendingFocusZoom := 17; FPendingFocusZoom := 17;
FDoFocusZoom := True; FDoFocusZoom := True;
FPendingFocusMarkerData := targetDs;
tmrLocate.Interval := 250; tmrLocate.Interval := 250;
tmrLocate.Enabled := True; tmrLocate.Enabled := True;
...@@ -687,8 +735,15 @@ begin ...@@ -687,8 +735,15 @@ begin
end; end;
end; end;
if found then FPendingUnitId := '';
FPendingUnitId := '';
if not found then
begin
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
ShowInformationModal('No Longer Available',
'This unit is no longer available on the map.');
end;
end; end;
procedure TFViewMap.ApplyPendingComplaintFocus; procedure TFViewMap.ApplyPendingComplaintFocus;
...@@ -716,6 +771,7 @@ begin ...@@ -716,6 +771,7 @@ begin
FPendingFocusCoord := coord; FPendingFocusCoord := coord;
FPendingFocusZoom := 17; FPendingFocusZoom := 17;
FDoFocusZoom := True; FDoFocusZoom := True;
FPendingFocusMarkerData := targetDs;
tmrLocate.Interval := 250; tmrLocate.Interval := 250;
tmrLocate.Enabled := True; tmrLocate.Enabled := True;
...@@ -725,8 +781,15 @@ begin ...@@ -725,8 +781,15 @@ begin
end; end;
end; end;
if found then FPendingComplaintId := '';
FPendingComplaintId := '';
if not found then
begin
FPendingFocusMarkerData := '';
FDoFocusZoom := False;
ShowInformationModal('No Longer Available',
'This incident is no longer active on the map.');
end;
end; end;
......
...@@ -282,6 +282,10 @@ ...@@ -282,6 +282,10 @@
<ResourceType>RCDATA</ResourceType> <ResourceType>RCDATA</ResourceType>
<ResourceId>PngImage_17</ResourceId> <ResourceId>PngImage_17</ResourceId>
</RcItem> </RcItem>
<RcItem Include="assets\markers\location_dot.png">
<ResourceType>RCDATA</ResourceType>
<ResourceId>LocationDot</ResourceId>
</RcItem>
<RcItem Include="assets\markers\default_hold.png"> <RcItem Include="assets\markers\default_hold.png">
<ResourceType>RCDATA</ResourceType> <ResourceType>RCDATA</ResourceType>
<ResourceId>PngImage_18</ResourceId> <ResourceId>PngImage_18</ResourceId>
...@@ -623,6 +627,12 @@ ...@@ -623,6 +627,12 @@
<Overwrite>true</Overwrite> <Overwrite>true</Overwrite>
</Platform> </Platform>
</DeployFile> </DeployFile>
<DeployFile LocalName="assets\markers\location_dot.png" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="assets\markers\default_hold.png" Configuration="Debug" Class="ProjectFile"> <DeployFile LocalName="assets\markers\default_hold.png" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32"> <Platform Name="Win32">
<RemoteDir>.\</RemoteDir> <RemoteDir>.\</RemoteDir>
......
...@@ -186,3 +186,12 @@ html, body { ...@@ -186,3 +186,12 @@ html, body {
} }
} }
/* Note: Keep navigation inside the visible mobile viewport and device safe areas. */
@supports (height: 100dvh) {
.emimobile-main { height: 100dvh !important; }
}
@supports (padding-top: env(safe-area-inset-top)) {
#top_nav { padding-top: calc(0.5rem + env(safe-area-inset-top)) !important; }
#bottom_nav { padding-bottom: calc(0.5rem + env(safe-area-inset-bottom)) !important; }
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<Projects Include="bfdEmiMobile.dproj"> <Projects Include="bfdEmiMobile.dproj">
<Dependencies/> <Dependencies/>
</Projects> </Projects>
<Projects Include="..\..\..\..\emiMobile_BFD\emiMobileServer\emiMobileServer.dproj"> <Projects Include="..\..\..\emiMobileServer.dproj">
<Dependencies/> <Dependencies/>
</Projects> </Projects>
</ItemGroup> </ItemGroup>
...@@ -27,13 +27,13 @@ ...@@ -27,13 +27,13 @@
<MSBuild Projects="bfdEmiMobile.dproj" Targets="Make"/> <MSBuild Projects="bfdEmiMobile.dproj" Targets="Make"/>
</Target> </Target>
<Target Name="emiMobileServer"> <Target Name="emiMobileServer">
<MSBuild Projects="..\..\..\..\emiMobile_BFD\emiMobileServer\emiMobileServer.dproj"/> <MSBuild Projects="..\..\..\emiMobileServer.dproj"/>
</Target> </Target>
<Target Name="emiMobileServer:Clean"> <Target Name="emiMobileServer:Clean">
<MSBuild Projects="..\..\..\..\emiMobile_BFD\emiMobileServer\emiMobileServer.dproj" Targets="Clean"/> <MSBuild Projects="..\..\..\emiMobileServer.dproj" Targets="Clean"/>
</Target> </Target>
<Target Name="emiMobileServer:Make"> <Target Name="emiMobileServer:Make">
<MSBuild Projects="..\..\..\..\emiMobile_BFD\emiMobileServer\emiMobileServer.dproj" Targets="Make"/> <MSBuild Projects="..\..\..\emiMobileServer.dproj" Targets="Make"/>
</Target> </Target>
<Target Name="Build"> <Target Name="Build">
<CallTarget Targets="bfdEmiMobile;emiMobileServer"/> <CallTarget Targets="bfdEmiMobile;emiMobileServer"/>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/> <meta content="width=device-width, initial-scale=1, viewport-fit=cover" name="viewport"/>
<noscript>Your browser does not support JavaScript!</noscript> <noscript>Your browser does not support JavaScript!</noscript>
<link href="data:;base64,=" rel="icon"/> <link href="data:;base64,=" rel="icon"/>
<title>emiMobile</title> <title>emiMobile</title>
......
...@@ -297,7 +297,7 @@ begin ...@@ -297,7 +297,7 @@ begin
} }
if (meta.complaintStatus === 'holdu') { if (meta.complaintStatus === 'holdu') {
return 'assets/markers/default_holdu.png'; return 'assets/markers/default_hold.png';
} }
if (meta.complaintStatus === 'suspend') { if (meta.complaintStatus === 'suspend') {
......
...@@ -51,13 +51,13 @@ type ...@@ -51,13 +51,13 @@ type
procedure ClearCredentialId; procedure ClearCredentialId;
// Section: Device registration verification // Section: Device registration verification
procedure BeginRegistration(APhoneNumber: string; procedure BeginRegistration(APhoneNumber, ADeviceType: string;
ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError); ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError);
procedure VerifyRegistrationCode(APhoneNumber, AVerificationCode: string; procedure VerifyRegistrationCode(APhoneNumber, ADeviceType, AVerificationCode: string;
ASuccess: TOnBeginSuccess; ASuccess: TOnBeginSuccess;
AError: TOnDeviceError); AError: TOnDeviceError);
// Section: WebAuthn registration // Section: WebAuthn registration
procedure CompleteRegistration(APhoneNumber, ACredentialId, procedure CompleteRegistration(APhoneNumber, ADeviceType, ACredentialId,
AAttestationObject, AClientDataJSON, AChallengeToken: string; AAttestationObject, AClientDataJSON, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
...@@ -74,7 +74,7 @@ type ...@@ -74,7 +74,7 @@ type
ASuccess: TOnLoginSuccess; AError: TOnLoginError); ASuccess: TOnLoginSuccess; AError: TOnLoginError);
// Simple-key registration and login // Simple-key registration and login
procedure CompleteRegistrationSimple(APhoneNumber, ADeviceKey, AChallengeToken: string; procedure CompleteRegistrationSimple(APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
procedure LoginDeviceKey(ACredentialId, AChallengeToken: string; procedure LoginDeviceKey(ACredentialId, AChallengeToken: string;
ASuccess: TOnLoginSuccess; AError: TOnLoginError); ASuccess: TOnLoginSuccess; AError: TOnLoginError);
...@@ -246,7 +246,7 @@ end; ...@@ -246,7 +246,7 @@ end;
// ---- WebAuthn registration ---- // ---- WebAuthn registration ----
procedure TAuthService.BeginRegistration(APhoneNumber: string; procedure TAuthService.BeginRegistration(APhoneNumber, ADeviceType: string;
ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError); ASuccess: TOnCodeSentSuccess; AError: TOnDeviceError);
procedure OnLoad(Response: TXDataClientResponse); procedure OnLoad(Response: TXDataClientResponse);
...@@ -272,12 +272,12 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string; ...@@ -272,12 +272,12 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.BeginRegistration', 'IAuthService.BeginRegistration',
[APhoneNumber], [APhoneNumber, ADeviceType],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
procedure TAuthService.VerifyRegistrationCode(APhoneNumber, procedure TAuthService.VerifyRegistrationCode(APhoneNumber, ADeviceType,
AVerificationCode: string; ASuccess: TOnBeginSuccess; AVerificationCode: string; ASuccess: TOnBeginSuccess;
AError: TOnDeviceError); AError: TOnDeviceError);
...@@ -306,12 +306,12 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber, ...@@ -306,12 +306,12 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber,
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.VerifyRegistrationCode', 'IAuthService.VerifyRegistrationCode',
[APhoneNumber, AVerificationCode], [APhoneNumber, ADeviceType, AVerificationCode],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId, procedure TAuthService.CompleteRegistration(APhoneNumber, ADeviceType, ACredentialId,
AAttestationObject, AClientDataJSON, AChallengeToken: string; AAttestationObject, AClientDataJSON, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
...@@ -345,7 +345,7 @@ procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId, ...@@ -345,7 +345,7 @@ procedure TAuthService.CompleteRegistration(APhoneNumber, ACredentialId,
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.CompleteRegistration', 'IAuthService.CompleteRegistration',
[APhoneNumber, ACredentialId, AAttestationObject, AClientDataJSON, AChallengeToken], [APhoneNumber, ADeviceType, ACredentialId, AAttestationObject, AClientDataJSON, AChallengeToken],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
...@@ -459,7 +459,7 @@ end; ...@@ -459,7 +459,7 @@ end;
// ---- Simple-key registration and login ---- // ---- Simple-key registration and login ----
procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, AChallengeToken: string; procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken: string;
ASuccess: TOnDeviceSuccess; AError: TOnDeviceError); ASuccess: TOnDeviceSuccess; AError: TOnDeviceError);
procedure OnLoad(Response: TXDataClientResponse); procedure OnLoad(Response: TXDataClientResponse);
...@@ -490,7 +490,7 @@ procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, ACha ...@@ -490,7 +490,7 @@ procedure TAuthService.CompleteRegistrationSimple(APhoneNumber, ADeviceKey, ACha
begin begin
FClient.RawInvoke( FClient.RawInvoke(
'IAuthService.CompleteRegistrationSimple', 'IAuthService.CompleteRegistrationSimple',
[APhoneNumber, ADeviceKey, AChallengeToken], [APhoneNumber, ADeviceType, ADeviceKey, AChallengeToken],
@OnLoad, @OnError @OnLoad, @OnError
); );
end; end;
......
...@@ -14,17 +14,17 @@ type ...@@ -14,17 +14,17 @@ type
// Note: A supplied connection is borrowed and must outlive this store. // Note: A supplied connection is borrowed and must outlive this store.
procedure UseConnection(AConnection: TUniConnection); virtual; abstract; procedure UseConnection(AConnection: TUniConnection); virtual; abstract;
property Connection: TUniConnection read GetConnection; property Connection: TUniConnection read GetConnection;
function PendingDevice(PhoneNumber: string): TDataSet; virtual; abstract; function PendingDevice(PhoneNumber: string; DeviceType: string): TDataSet; virtual; abstract;
function SetVerificationCode(Code: string; PhoneNumber: string): Int64; virtual; abstract; function SetVerificationCode(Code: string; PhoneNumber: string; DeviceType: string): Int64; virtual; abstract;
function ConsumeVerificationCode(PhoneNumber: string; Code: string): Int64; virtual; abstract; function ConsumeVerificationCode(PhoneNumber: string; Code: string; DeviceType: string): Int64; virtual; abstract;
function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string): Int64; virtual; abstract; function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string; DeviceType: string): Int64; virtual; abstract;
function UnrevokedDeviceUser(CredentialId: string): TDataSet; virtual; abstract; function UnrevokedDeviceUser(CredentialId: string): TDataSet; virtual; abstract;
function ActiveDeviceUser(CredentialId: string): TDataSet; virtual; abstract; function ActiveDeviceUser(CredentialId: string): TDataSet; virtual; abstract;
function UnrevokedDeviceKey(CredentialId: string): TDataSet; virtual; abstract; function UnrevokedDeviceKey(CredentialId: string): TDataSet; virtual; abstract;
function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; virtual; abstract; function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; virtual; abstract;
function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; virtual; abstract; function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; virtual; abstract;
function ActiveDeviceKey(CredentialId: string): TDataSet; virtual; abstract; function ActiveDeviceKey(CredentialId: string): TDataSet; virtual; abstract;
function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string): Int64; virtual; abstract; function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string; DeviceType: string): Int64; virtual; abstract;
function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; virtual; abstract; function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; virtual; abstract;
function ActiveSimpleDevice(CredentialId: string): TDataSet; virtual; abstract; function ActiveSimpleDevice(CredentialId: string): TDataSet; virtual; abstract;
function DeviceStatus(CredentialId: string): TDataSet; virtual; abstract; function DeviceStatus(CredentialId: string): TDataSet; virtual; abstract;
...@@ -32,7 +32,7 @@ type ...@@ -32,7 +32,7 @@ type
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; virtual; abstract; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; virtual; abstract;
function UnrevokeDevice(CredentialId: string): Int64; virtual; abstract; function UnrevokeDevice(CredentialId: string): Int64; virtual; abstract;
function DeviceNameExists(DeviceName: string): Boolean; virtual; abstract; function DeviceNameExists(DeviceName: string): Boolean; virtual; abstract;
function PendingPhone(PhoneNumber: string): TDataSet; virtual; abstract; function PendingPhone(PhoneNumber: string; DeviceType: string): TDataSet; virtual; abstract;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; virtual; abstract; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; virtual; abstract;
function DeleteDevice(DeviceId: Integer): Int64; virtual; abstract; function DeleteDevice(DeviceId: Integer): Int64; virtual; abstract;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; virtual; abstract; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; virtual; abstract;
......
...@@ -18,7 +18,8 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -18,7 +18,8 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'SELECT ID, DEVICE_NAME' 'SELECT ID, DEVICE_NAME'
'FROM DEVICE_REGISTRATIONS' 'FROM DEVICE_REGISTRATIONS'
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''') ' AND STATUS = ''pending'''
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
ReadOnly = True ReadOnly = True
Active = False Active = False
Left = 60 Left = 60
...@@ -26,6 +27,11 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -26,6 +27,11 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
end> end>
...@@ -36,13 +42,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -36,13 +42,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'UPDATE DEVICE_REGISTRATIONS' 'UPDATE DEVICE_REGISTRATIONS'
'SET VERIFICATION_CODE = :CODE, VERIFICATION_EXPIRES_AT = SYSTIMESTAMP + INTERVAL ''5'' MINUTE' 'SET VERIFICATION_CODE = :CODE, VERIFICATION_EXPIRES_AT = SYSTIMESTAMP + INTERVAL ''5'' MINUTE'
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''') ' AND STATUS = ''pending'''
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Active = False Active = False
Left = 250 Left = 250
Top = 110 Top = 110
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CODE' Name = 'CODE'
ParamType = ptInput ParamType = ptInput
end end
...@@ -60,13 +72,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -60,13 +72,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''' ' AND STATUS = ''pending'''
' AND VERIFICATION_CODE = :CODE' ' AND VERIFICATION_CODE = :CODE'
' AND VERIFICATION_EXPIRES_AT > SYSTIMESTAMP') ' AND VERIFICATION_EXPIRES_AT > SYSTIMESTAMP'
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Active = False Active = False
Left = 630 Left = 630
Top = 110 Top = 110
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
end end
...@@ -82,13 +100,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -82,13 +100,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'UPDATE DEVICE_REGISTRATIONS' 'UPDATE DEVICE_REGISTRATIONS'
'SET CREDENTIAL_ID = :CID, USER_AGENT = :AGENT, PUBLIC_KEY_X = :KEYX, PUBLIC_KEY_Y = :KEYY, PUBLIC_KEY_ALG = :ALG, SIGN_COUNT = :CNT, VERIFICATION_CODE = NULL, VERIFICATION_EXPIRES_AT = NULL, STATUS = ''active'', REGISTERED_AT = SYSTIMESTAMP' 'SET CREDENTIAL_ID = :CID, USER_AGENT = :AGENT, PUBLIC_KEY_X = :KEYX, PUBLIC_KEY_Y = :KEYY, PUBLIC_KEY_ALG = :ALG, SIGN_COUNT = :CNT, VERIFICATION_CODE = NULL, VERIFICATION_EXPIRES_AT = NULL, STATUS = ''active'', REGISTERED_AT = SYSTIMESTAMP'
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''') ' AND STATUS = ''pending'''
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Active = False Active = False
Left = 820 Left = 820
Top = 110 Top = 110
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CID' Name = 'CID'
ParamType = ptInput ParamType = ptInput
end end
...@@ -250,13 +274,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -250,13 +274,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'UPDATE DEVICE_REGISTRATIONS' 'UPDATE DEVICE_REGISTRATIONS'
'SET CREDENTIAL_ID = :CID, USER_AGENT = :AGENT, KEY_TYPE = ''simple'', VERIFICATION_CODE = NULL, VERIFICATION_EXPIRES_AT = NULL, STATUS = ''active'', REGISTERED_AT = SYSTIMESTAMP' 'SET CREDENTIAL_ID = :CID, USER_AGENT = :AGENT, KEY_TYPE = ''simple'', VERIFICATION_CODE = NULL, VERIFICATION_EXPIRES_AT = NULL, STATUS = ''active'', REGISTERED_AT = SYSTIMESTAMP'
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''') ' AND STATUS = ''pending'''
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Active = False Active = False
Left = 250 Left = 250
Top = 330 Top = 330
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CID' Name = 'CID'
ParamType = ptInput ParamType = ptInput
end end
...@@ -398,7 +428,8 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -398,7 +428,8 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
'SELECT ID' 'SELECT ID'
'FROM DEVICE_REGISTRATIONS' 'FROM DEVICE_REGISTRATIONS'
'WHERE PHONE_NUMBER = :PHONE' 'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''') ' AND STATUS = ''pending'''
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
ReadOnly = True ReadOnly = True
Active = False Active = False
Left = 630 Left = 630
...@@ -406,6 +437,11 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -406,6 +437,11 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
end> end>
......
...@@ -40,17 +40,17 @@ type ...@@ -40,17 +40,17 @@ type
function GetConnection: TUniConnection; override; function GetConnection: TUniConnection; override;
public public
procedure UseConnection(AConnection: TUniConnection); override; procedure UseConnection(AConnection: TUniConnection); override;
function PendingDevice(PhoneNumber: string): TDataSet; override; function PendingDevice(PhoneNumber: string; DeviceType: string): TDataSet; override;
function SetVerificationCode(Code: string; PhoneNumber: string): Int64; override; function SetVerificationCode(Code: string; PhoneNumber: string; DeviceType: string): Int64; override;
function ConsumeVerificationCode(PhoneNumber: string; Code: string): Int64; override; function ConsumeVerificationCode(PhoneNumber: string; Code: string; DeviceType: string): Int64; override;
function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string): Int64; override; function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string; DeviceType: string): Int64; override;
function UnrevokedDeviceUser(CredentialId: string): TDataSet; override; function UnrevokedDeviceUser(CredentialId: string): TDataSet; override;
function ActiveDeviceUser(CredentialId: string): TDataSet; override; function ActiveDeviceUser(CredentialId: string): TDataSet; override;
function UnrevokedDeviceKey(CredentialId: string): TDataSet; override; function UnrevokedDeviceKey(CredentialId: string): TDataSet; override;
function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; override; function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; override;
function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; override; function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; override;
function ActiveDeviceKey(CredentialId: string): TDataSet; override; function ActiveDeviceKey(CredentialId: string): TDataSet; override;
function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string): Int64; override; function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string; DeviceType: string): Int64; override;
function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; override; function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; override;
function ActiveSimpleDevice(CredentialId: string): TDataSet; override; function ActiveSimpleDevice(CredentialId: string): TDataSet; override;
function DeviceStatus(CredentialId: string): TDataSet; override; function DeviceStatus(CredentialId: string): TDataSet; override;
...@@ -58,7 +58,7 @@ type ...@@ -58,7 +58,7 @@ type
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override;
function UnrevokeDevice(CredentialId: string): Int64; override; function UnrevokeDevice(CredentialId: string): Int64; override;
function DeviceNameExists(DeviceName: string): Boolean; override; function DeviceNameExists(DeviceName: string): Boolean; override;
function PendingPhone(PhoneNumber: string): TDataSet; override; function PendingPhone(PhoneNumber: string; DeviceType: string): TDataSet; override;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override;
function DeleteDevice(DeviceId: Integer): Int64; override; function DeleteDevice(DeviceId: Integer): Int64; override;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override;
...@@ -93,33 +93,36 @@ begin ...@@ -93,33 +93,36 @@ begin
TUniQuery(Components[i]).Connection := Connection; TUniQuery(Components[i]).Connection := Connection;
end; end;
function TOracleDeviceDatabase.PendingDevice(PhoneNumber: string): TDataSet; function TOracleDeviceDatabase.PendingDevice(PhoneNumber: string; DeviceType: string): TDataSet;
begin begin
uqPendingDevice.Close; uqPendingDevice.Close;
uqPendingDevice.ParamByName('PHONE').AsString := PhoneNumber; uqPendingDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqPendingDevice.ParamByName('TYPE').AsString := DeviceType;
uqPendingDevice.Open; uqPendingDevice.Open;
Result := uqPendingDevice; Result := uqPendingDevice;
end; end;
function TOracleDeviceDatabase.SetVerificationCode(Code: string; PhoneNumber: string): Int64; function TOracleDeviceDatabase.SetVerificationCode(Code: string; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqSetVerificationCode.Close; uqSetVerificationCode.Close;
uqSetVerificationCode.ParamByName('CODE').AsString := Code; uqSetVerificationCode.ParamByName('CODE').AsString := Code;
uqSetVerificationCode.ParamByName('PHONE').AsString := PhoneNumber; uqSetVerificationCode.ParamByName('PHONE').AsString := PhoneNumber;
uqSetVerificationCode.ParamByName('TYPE').AsString := DeviceType;
uqSetVerificationCode.ExecSQL; uqSetVerificationCode.ExecSQL;
Result := uqSetVerificationCode.RowsAffected; Result := uqSetVerificationCode.RowsAffected;
end; end;
function TOracleDeviceDatabase.ConsumeVerificationCode(PhoneNumber: string; Code: string): Int64; function TOracleDeviceDatabase.ConsumeVerificationCode(PhoneNumber: string; Code: string; DeviceType: string): Int64;
begin begin
uqConsumeVerificationCode.Close; uqConsumeVerificationCode.Close;
uqConsumeVerificationCode.ParamByName('PHONE').AsString := PhoneNumber; uqConsumeVerificationCode.ParamByName('PHONE').AsString := PhoneNumber;
uqConsumeVerificationCode.ParamByName('TYPE').AsString := DeviceType;
uqConsumeVerificationCode.ParamByName('CODE').AsString := Code; uqConsumeVerificationCode.ParamByName('CODE').AsString := Code;
uqConsumeVerificationCode.ExecSQL; uqConsumeVerificationCode.ExecSQL;
Result := uqConsumeVerificationCode.RowsAffected; Result := uqConsumeVerificationCode.RowsAffected;
end; end;
function TOracleDeviceDatabase.ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string): Int64; function TOracleDeviceDatabase.ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqActivateWebAuthnDevice.Close; uqActivateWebAuthnDevice.Close;
uqActivateWebAuthnDevice.ParamByName('CID').AsString := CredentialId; uqActivateWebAuthnDevice.ParamByName('CID').AsString := CredentialId;
...@@ -129,6 +132,7 @@ begin ...@@ -129,6 +132,7 @@ begin
uqActivateWebAuthnDevice.ParamByName('ALG').AsInteger := PublicKeyAlg; uqActivateWebAuthnDevice.ParamByName('ALG').AsInteger := PublicKeyAlg;
uqActivateWebAuthnDevice.ParamByName('CNT').AsInteger := SignCount; uqActivateWebAuthnDevice.ParamByName('CNT').AsInteger := SignCount;
uqActivateWebAuthnDevice.ParamByName('PHONE').AsString := PhoneNumber; uqActivateWebAuthnDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqActivateWebAuthnDevice.ParamByName('TYPE').AsString := DeviceType;
uqActivateWebAuthnDevice.ExecSQL; uqActivateWebAuthnDevice.ExecSQL;
Result := uqActivateWebAuthnDevice.RowsAffected; Result := uqActivateWebAuthnDevice.RowsAffected;
end; end;
...@@ -184,12 +188,13 @@ begin ...@@ -184,12 +188,13 @@ begin
Result := uqActiveDeviceKey; Result := uqActiveDeviceKey;
end; end;
function TOracleDeviceDatabase.ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string): Int64; function TOracleDeviceDatabase.ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqActivateSimpleDevice.Close; uqActivateSimpleDevice.Close;
uqActivateSimpleDevice.ParamByName('CID').AsString := CredentialId; uqActivateSimpleDevice.ParamByName('CID').AsString := CredentialId;
uqActivateSimpleDevice.ParamByName('AGENT').AsString := UserAgent; uqActivateSimpleDevice.ParamByName('AGENT').AsString := UserAgent;
uqActivateSimpleDevice.ParamByName('PHONE').AsString := PhoneNumber; uqActivateSimpleDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqActivateSimpleDevice.ParamByName('TYPE').AsString := DeviceType;
uqActivateSimpleDevice.ExecSQL; uqActivateSimpleDevice.ExecSQL;
Result := uqActivateSimpleDevice.RowsAffected; Result := uqActivateSimpleDevice.RowsAffected;
end; end;
...@@ -254,10 +259,11 @@ begin ...@@ -254,10 +259,11 @@ begin
end; end;
end; end;
function TOracleDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet; function TOracleDeviceDatabase.PendingPhone(PhoneNumber: string; DeviceType: string): TDataSet;
begin begin
uqPendingPhone.Close; uqPendingPhone.Close;
uqPendingPhone.ParamByName('PHONE').AsString := PhoneNumber; uqPendingPhone.ParamByName('PHONE').AsString := PhoneNumber;
uqPendingPhone.ParamByName('TYPE').AsString := DeviceType;
uqPendingPhone.Open; uqPendingPhone.Open;
Result := uqPendingPhone; Result := uqPendingPhone;
end; end;
......
...@@ -16,12 +16,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -16,12 +16,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
SQL.Strings = ( SQL.Strings = (
'SELECT id, device_name FROM lems.device_registrations WHERE phon' + 'SELECT id, device_name FROM lems.device_registrations WHERE phon' +
'e_number = :PHONE AND status = '#39'pending'#39) 'e_number = :PHONE AND status = '#39'pending'#39
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 60 Left = 60
Top = 110 Top = 110
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
...@@ -33,12 +39,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -33,12 +39,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
'UPDATE lems.device_registrations SET verification_code = :CODE, ' + 'UPDATE lems.device_registrations SET verification_code = :CODE, ' +
' verification_expires_at = NOW() + INTERVAL '#39'5 minutes'#39' WHERE' + ' verification_expires_at = NOW() + INTERVAL '#39'5 minutes'#39' WHERE' +
' phone_number = :PHONE AND status = '#39'pending'#39) ' phone_number = :PHONE AND status = '#39'pending'#39
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 188 Left = 188
Top = 178 Top = 178
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CODE' Name = 'CODE'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
...@@ -57,12 +69,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -57,12 +69,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
'UPDATE lems.device_registrations SET verification_code = NULL, v' + 'UPDATE lems.device_registrations SET verification_code = NULL, v' +
'erification_expires_at = NULL WHERE phone_number = :PHONE AND st' + 'erification_expires_at = NULL WHERE phone_number = :PHONE AND st' +
'atus = '#39'pending'#39' AND verification_code = :CODE AND verification_' + 'atus = '#39'pending'#39' AND verification_code = :CODE AND verification_' +
'expires_at > NOW()') 'expires_at > NOW()'
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 502 Left = 502
Top = 130 Top = 130
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
...@@ -83,12 +101,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -83,12 +101,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
' public_key_alg = :ALG, sign_count = :CNT, verification_c' + ' public_key_alg = :ALG, sign_count = :CNT, verification_c' +
'ode = NULL, verification_expires_at = NULL, status = '#39'active' + 'ode = NULL, verification_expires_at = NULL, status = '#39'active' +
#39', registered_at = NOW() WHERE phone_number = :PHONE AND status ' + #39', registered_at = NOW() WHERE phone_number = :PHONE AND status ' +
'= '#39'pending'#39) '= '#39'pending'#39
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 618 Left = 618
Top = 164 Top = 164
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CID' Name = 'CID'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
...@@ -255,12 +279,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -255,12 +279,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
'agent = :AGENT, key_type = '#39'simple'#39', verification_code =' + 'agent = :AGENT, key_type = '#39'simple'#39', verification_code =' +
' NULL, verification_expires_at = NULL, status = '#39'active'#39', re' + ' NULL, verification_expires_at = NULL, status = '#39'active'#39', re' +
'gistered_at = NOW() WHERE phone_number = :PHONE AND status = '#39'pe' + 'gistered_at = NOW() WHERE phone_number = :PHONE AND status = '#39'pe' +
'nding'#39) 'nding'#39
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 190 Left = 190
Top = 364 Top = 364
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'CID' Name = 'CID'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
...@@ -399,12 +429,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -399,12 +429,18 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
SQL.Strings = ( SQL.Strings = (
'SELECT id FROM lems.device_registrations WHERE phone_number = :P' + 'SELECT id FROM lems.device_registrations WHERE phone_number = :P' +
'HONE AND status = '#39'pending'#39) 'HONE AND status = '#39'pending'#39
' AND LOWER(TRIM(DEVICE_TYPE)) = :TYPE')
Left = 470 Left = 470
Top = 458 Top = 458
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftString
Name = 'TYPE'
ParamType = ptInput
end
item
DataType = ftString
Name = 'PHONE' Name = 'PHONE'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
......
...@@ -41,17 +41,17 @@ type ...@@ -41,17 +41,17 @@ type
function GetConnection: TUniConnection; override; function GetConnection: TUniConnection; override;
public public
procedure UseConnection(AConnection: TUniConnection); override; procedure UseConnection(AConnection: TUniConnection); override;
function PendingDevice(PhoneNumber: string): TDataSet; override; function PendingDevice(PhoneNumber: string; DeviceType: string): TDataSet; override;
function SetVerificationCode(Code: string; PhoneNumber: string): Int64; override; function SetVerificationCode(Code: string; PhoneNumber: string; DeviceType: string): Int64; override;
function ConsumeVerificationCode(PhoneNumber: string; Code: string): Int64; override; function ConsumeVerificationCode(PhoneNumber: string; Code: string; DeviceType: string): Int64; override;
function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string): Int64; override; function ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string; DeviceType: string): Int64; override;
function UnrevokedDeviceUser(CredentialId: string): TDataSet; override; function UnrevokedDeviceUser(CredentialId: string): TDataSet; override;
function ActiveDeviceUser(CredentialId: string): TDataSet; override; function ActiveDeviceUser(CredentialId: string): TDataSet; override;
function UnrevokedDeviceKey(CredentialId: string): TDataSet; override; function UnrevokedDeviceKey(CredentialId: string): TDataSet; override;
function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; override; function UpdateSignCount(SignCount: Integer; CredentialId: string): Int64; override;
function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; override; function SaveDeviceUser(Username: string; Agency: string; CredentialId: string): Int64; override;
function ActiveDeviceKey(CredentialId: string): TDataSet; override; function ActiveDeviceKey(CredentialId: string): TDataSet; override;
function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string): Int64; override; function ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string; DeviceType: string): Int64; override;
function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; override; function ActiveSimpleDeviceUser(CredentialId: string): TDataSet; override;
function ActiveSimpleDevice(CredentialId: string): TDataSet; override; function ActiveSimpleDevice(CredentialId: string): TDataSet; override;
function DeviceStatus(CredentialId: string): TDataSet; override; function DeviceStatus(CredentialId: string): TDataSet; override;
...@@ -59,7 +59,7 @@ type ...@@ -59,7 +59,7 @@ type
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override;
function UnrevokeDevice(CredentialId: string): Int64; override; function UnrevokeDevice(CredentialId: string): Int64; override;
function DeviceNameExists(DeviceName: string): Boolean; override; function DeviceNameExists(DeviceName: string): Boolean; override;
function PendingPhone(PhoneNumber: string): TDataSet; override; function PendingPhone(PhoneNumber: string; DeviceType: string): TDataSet; override;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override;
function DeleteDevice(DeviceId: Integer): Int64; override; function DeleteDevice(DeviceId: Integer): Int64; override;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override;
...@@ -94,33 +94,36 @@ begin ...@@ -94,33 +94,36 @@ begin
TUniQuery(Components[i]).Connection := Connection; TUniQuery(Components[i]).Connection := Connection;
end; end;
function TPostgreSQLDeviceDatabase.PendingDevice(PhoneNumber: string): TDataSet; function TPostgreSQLDeviceDatabase.PendingDevice(PhoneNumber: string; DeviceType: string): TDataSet;
begin begin
uqPendingDevice.Close; uqPendingDevice.Close;
uqPendingDevice.ParamByName('PHONE').AsString := PhoneNumber; uqPendingDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqPendingDevice.ParamByName('TYPE').AsString := DeviceType;
uqPendingDevice.Open; uqPendingDevice.Open;
Result := uqPendingDevice; Result := uqPendingDevice;
end; end;
function TPostgreSQLDeviceDatabase.SetVerificationCode(Code: string; PhoneNumber: string): Int64; function TPostgreSQLDeviceDatabase.SetVerificationCode(Code: string; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqSetVerificationCode.Close; uqSetVerificationCode.Close;
uqSetVerificationCode.ParamByName('CODE').AsString := Code; uqSetVerificationCode.ParamByName('CODE').AsString := Code;
uqSetVerificationCode.ParamByName('PHONE').AsString := PhoneNumber; uqSetVerificationCode.ParamByName('PHONE').AsString := PhoneNumber;
uqSetVerificationCode.ParamByName('TYPE').AsString := DeviceType;
uqSetVerificationCode.ExecSQL; uqSetVerificationCode.ExecSQL;
Result := uqSetVerificationCode.RowsAffected; Result := uqSetVerificationCode.RowsAffected;
end; end;
function TPostgreSQLDeviceDatabase.ConsumeVerificationCode(PhoneNumber: string; Code: string): Int64; function TPostgreSQLDeviceDatabase.ConsumeVerificationCode(PhoneNumber: string; Code: string; DeviceType: string): Int64;
begin begin
uqConsumeVerificationCode.Close; uqConsumeVerificationCode.Close;
uqConsumeVerificationCode.ParamByName('PHONE').AsString := PhoneNumber; uqConsumeVerificationCode.ParamByName('PHONE').AsString := PhoneNumber;
uqConsumeVerificationCode.ParamByName('TYPE').AsString := DeviceType;
uqConsumeVerificationCode.ParamByName('CODE').AsString := Code; uqConsumeVerificationCode.ParamByName('CODE').AsString := Code;
uqConsumeVerificationCode.ExecSQL; uqConsumeVerificationCode.ExecSQL;
Result := uqConsumeVerificationCode.RowsAffected; Result := uqConsumeVerificationCode.RowsAffected;
end; end;
function TPostgreSQLDeviceDatabase.ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string): Int64; function TPostgreSQLDeviceDatabase.ActivateWebAuthnDevice(CredentialId: string; UserAgent: string; PublicKeyX: TBytes; PublicKeyY: TBytes; PublicKeyAlg: Integer; SignCount: Integer; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqActivateWebAuthnDevice.Close; uqActivateWebAuthnDevice.Close;
uqActivateWebAuthnDevice.ParamByName('CID').AsString := CredentialId; uqActivateWebAuthnDevice.ParamByName('CID').AsString := CredentialId;
...@@ -130,6 +133,7 @@ begin ...@@ -130,6 +133,7 @@ begin
uqActivateWebAuthnDevice.ParamByName('ALG').AsInteger := PublicKeyAlg; uqActivateWebAuthnDevice.ParamByName('ALG').AsInteger := PublicKeyAlg;
uqActivateWebAuthnDevice.ParamByName('CNT').AsInteger := SignCount; uqActivateWebAuthnDevice.ParamByName('CNT').AsInteger := SignCount;
uqActivateWebAuthnDevice.ParamByName('PHONE').AsString := PhoneNumber; uqActivateWebAuthnDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqActivateWebAuthnDevice.ParamByName('TYPE').AsString := DeviceType;
uqActivateWebAuthnDevice.ExecSQL; uqActivateWebAuthnDevice.ExecSQL;
Result := uqActivateWebAuthnDevice.RowsAffected; Result := uqActivateWebAuthnDevice.RowsAffected;
end; end;
...@@ -185,12 +189,13 @@ begin ...@@ -185,12 +189,13 @@ begin
Result := uqActiveDeviceKey; Result := uqActiveDeviceKey;
end; end;
function TPostgreSQLDeviceDatabase.ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string): Int64; function TPostgreSQLDeviceDatabase.ActivateSimpleDevice(CredentialId: string; UserAgent: string; PhoneNumber: string; DeviceType: string): Int64;
begin begin
uqActivateSimpleDevice.Close; uqActivateSimpleDevice.Close;
uqActivateSimpleDevice.ParamByName('CID').AsString := CredentialId; uqActivateSimpleDevice.ParamByName('CID').AsString := CredentialId;
uqActivateSimpleDevice.ParamByName('AGENT').AsString := UserAgent; uqActivateSimpleDevice.ParamByName('AGENT').AsString := UserAgent;
uqActivateSimpleDevice.ParamByName('PHONE').AsString := PhoneNumber; uqActivateSimpleDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqActivateSimpleDevice.ParamByName('TYPE').AsString := DeviceType;
uqActivateSimpleDevice.ExecSQL; uqActivateSimpleDevice.ExecSQL;
Result := uqActivateSimpleDevice.RowsAffected; Result := uqActivateSimpleDevice.RowsAffected;
end; end;
...@@ -255,10 +260,11 @@ begin ...@@ -255,10 +260,11 @@ begin
end; end;
end; end;
function TPostgreSQLDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet; function TPostgreSQLDeviceDatabase.PendingPhone(PhoneNumber: string; DeviceType: string): TDataSet;
begin begin
uqPendingPhone.Close; uqPendingPhone.Close;
uqPendingPhone.ParamByName('PHONE').AsString := PhoneNumber; uqPendingPhone.ParamByName('PHONE').AsString := PhoneNumber;
uqPendingPhone.ParamByName('TYPE').AsString := DeviceType;
uqPendingPhone.Open; uqPendingPhone.Open;
Result := uqPendingPhone; Result := uqPendingPhone;
end; end;
......
...@@ -533,13 +533,13 @@ begin ...@@ -533,13 +533,13 @@ begin
Exit; Exit;
end; end;
// Reject if a pending entry with this phone already exists // Note: A phone number can have one pending registration per device type.
q := store.PendingPhone(normalizedPhone); q := store.PendingPhone(normalizedPhone, normalizedDeviceType);
if not q.IsEmpty then if not q.IsEmpty then
begin begin
q.Close; q.Close;
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'A pending entry for that phone number already exists.'); Result.AddPair('message', 'A pending entry for that phone number and device type already exists.');
Exit; Exit;
end; end;
q.Close; q.Close;
......
...@@ -79,12 +79,12 @@ type ...@@ -79,12 +79,12 @@ type
function VerifyVersion(ClientVersion: string): TJSONObject; function VerifyVersion(ClientVersion: string): TJSONObject;
// Section: Device registration step 1 checks phone pre-auth and sends an SMS code // Section: Device registration step 1 checks phone pre-auth and sends an SMS code
function BeginRegistration(const PhoneNumber: string): TJSONObject; function BeginRegistration(const PhoneNumber, DeviceType: string): TJSONObject;
// Section: Device registration step 2 verifies the SMS code and returns a challenge // Section: Device registration step 2 verifies the SMS code and returns a challenge
function VerifyRegistrationCode(const PhoneNumber, function VerifyRegistrationCode(const PhoneNumber, DeviceType,
VerificationCode: string): TJSONObject; VerificationCode: string): TJSONObject;
// Section: WebAuthn registration verifies the credential and activates the pending row // Section: WebAuthn registration verifies the credential and activates the pending row
function CompleteRegistration(const PhoneNumber, CredentialId, function CompleteRegistration(const PhoneNumber, DeviceType, CredentialId,
AttestationObject, ClientDataJSON, AttestationObject, ClientDataJSON,
ChallengeToken: string): TJSONObject; ChallengeToken: string): TJSONObject;
// WebAuthn authentication challenge — called before Login // WebAuthn authentication challenge — called before Login
...@@ -96,7 +96,7 @@ type ...@@ -96,7 +96,7 @@ type
AuthenticatorData, ClientDataJSON, AuthenticatorData, ClientDataJSON,
Signature: string): string; Signature: string): string;
// Simple-key registration — client generates key, no crypto verification // Simple-key registration — client generates key, no crypto verification
function CompleteRegistrationSimple(const PhoneNumber, DeviceKey, function CompleteRegistrationSimple(const PhoneNumber, DeviceType, DeviceKey,
ChallengeToken: string): TJSONObject; ChallengeToken: string): TJSONObject;
// Simple-key auto-login — verifies challenge token + credential ownership // Simple-key auto-login — verifies challenge token + credential ownership
function LoginDeviceKey(const CredentialId, ChallengeToken: string): string; function LoginDeviceKey(const CredentialId, ChallengeToken: string): string;
......
...@@ -41,16 +41,16 @@ type ...@@ -41,16 +41,16 @@ type
destructor Destroy; override; destructor Destroy; override;
function GetAgencieslist(): TAgenciesList; function GetAgencieslist(): TAgenciesList;
function GetAgencyConfiglist: TAgencyConfigList; function GetAgencyConfiglist: TAgencyConfigList;
function BeginRegistration(const PhoneNumber: string): TJSONObject; function BeginRegistration(const PhoneNumber, DeviceType: string): TJSONObject;
function VerifyRegistrationCode(const PhoneNumber, function VerifyRegistrationCode(const PhoneNumber, DeviceType,
VerificationCode: string): TJSONObject; VerificationCode: string): TJSONObject;
function CompleteRegistration(const PhoneNumber, CredentialId, AttestationObject, ClientDataJSON, ChallengeToken: string): TJSONObject; function CompleteRegistration(const PhoneNumber, DeviceType, CredentialId, AttestationObject, ClientDataJSON, ChallengeToken: string): TJSONObject;
function BeginAuthentication(const CredentialId: string): TJSONObject; function BeginAuthentication(const CredentialId: string): TJSONObject;
function GetDeviceUser(const CredentialId: string): TJSONObject; function GetDeviceUser(const CredentialId: string): TJSONObject;
function LoginAutomatic(const CredentialId, ChallengeToken, function LoginAutomatic(const CredentialId, ChallengeToken,
AuthenticatorData, ClientDataJSON, AuthenticatorData, ClientDataJSON,
Signature: string): string; Signature: string): string;
function CompleteRegistrationSimple(const PhoneNumber, DeviceKey, function CompleteRegistrationSimple(const PhoneNumber, DeviceType, DeviceKey,
ChallengeToken: string): TJSONObject; ChallengeToken: string): TJSONObject;
function LoginDeviceKey(const CredentialId, ChallengeToken: string): string; function LoginDeviceKey(const CredentialId, ChallengeToken: string): string;
function LoginPasswordAndSimpleKey(const User, Password, Agency, function LoginPasswordAndSimpleKey(const User, Password, Agency,
...@@ -257,7 +257,7 @@ end; ...@@ -257,7 +257,7 @@ end;
// BeginRegistration // BeginRegistration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function TAuthService.BeginRegistration(const PhoneNumber: string): TJSONObject; function TAuthService.BeginRegistration(const PhoneNumber, DeviceType: string): TJSONObject;
var var
affectedRows: Int64; affectedRows: Int64;
code, normalizedPhone, deviceName: string; code, normalizedPhone, deviceName: string;
...@@ -287,14 +287,24 @@ begin ...@@ -287,14 +287,24 @@ begin
end; end;
end; end;
if (not SameText(Trim(DeviceType), 'iphone')) and
(not SameText(Trim(DeviceType), 'android')) and
(not SameText(Trim(DeviceType), 'browser')) and
(not SameText(Trim(DeviceType), 'other')) then
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'Please select a valid device type.');
Exit;
end;
// Verify phone number was pre-authorized by admin // Verify phone number was pre-authorized by admin
q := FDeviceStore.PendingDevice(normalizedPhone); q := FDeviceStore.PendingDevice(normalizedPhone, LowerCase(Trim(DeviceType)));
if q.IsEmpty then if q.IsEmpty then
begin begin
q.Close; q.Close;
Logger.Log(2, 'BeginRegistration - phone not pre-authorized: "' + normalizedPhone + '"'); Logger.Log(2, 'BeginRegistration - phone not pre-authorized: "' + normalizedPhone + '"');
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'Phone number not recognized. Contact your administrator.'); Result.AddPair('message', 'No pending registration matches that phone number and device type. Contact your administrator.');
Exit; Exit;
end; end;
deviceId := q.FieldByName('id').AsInteger; deviceId := q.FieldByName('id').AsInteger;
...@@ -320,11 +330,11 @@ begin ...@@ -320,11 +330,11 @@ begin
end; end;
// Note: Persist the code only after the SMS provider accepts the message. // Note: Persist the code only after the SMS provider accepts the message.
affectedRows := FDeviceStore.SetVerificationCode(code, normalizedPhone); affectedRows := FDeviceStore.SetVerificationCode(code, normalizedPhone, LowerCase(Trim(DeviceType)));
if affectedRows = 0 then if affectedRows = 0 then
begin begin
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'Phone number not recognized. Contact your administrator.'); Result.AddPair('message', 'No pending registration matches that phone number and device type. Contact your administrator.');
Exit; Exit;
end; end;
...@@ -336,7 +346,7 @@ begin ...@@ -336,7 +346,7 @@ begin
Logger.Log(3, '---TAuthService.BeginRegistration End'); Logger.Log(3, '---TAuthService.BeginRegistration End');
end; end;
function TAuthService.VerifyRegistrationCode(const PhoneNumber, function TAuthService.VerifyRegistrationCode(const PhoneNumber, DeviceType,
VerificationCode: string): TJSONObject; VerificationCode: string): TJSONObject;
var var
affectedRows: Int64; affectedRows: Int64;
...@@ -360,7 +370,7 @@ begin ...@@ -360,7 +370,7 @@ begin
verificationValid := False; verificationValid := False;
if Length(Trim(VerificationCode)) = 6 then if Length(Trim(VerificationCode)) = 6 then
begin begin
affectedRows := FDeviceStore.ConsumeVerificationCode(normalizedPhone, Trim(VerificationCode)); affectedRows := FDeviceStore.ConsumeVerificationCode(normalizedPhone, Trim(VerificationCode), LowerCase(Trim(DeviceType)));
verificationValid := affectedRows > 0; verificationValid := affectedRows > 0;
end; end;
...@@ -373,7 +383,7 @@ begin ...@@ -373,7 +383,7 @@ begin
Exit; Exit;
end; end;
token := MakeChallengeToken('reg-' + normalizedPhone); token := MakeChallengeToken('reg-' + normalizedPhone + '-' + LowerCase(Trim(DeviceType)));
challengeB64 := token.Split([':'], 4)[0]; challengeB64 := token.Split([':'], 4)[0];
Result.AddPair('status', 'ok'); Result.AddPair('status', 'ok');
...@@ -389,7 +399,7 @@ end; ...@@ -389,7 +399,7 @@ end;
// CompleteRegistration // CompleteRegistration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function TAuthService.CompleteRegistration(const PhoneNumber, CredentialId, function TAuthService.CompleteRegistration(const PhoneNumber, DeviceType, CredentialId,
AttestationObject, ClientDataJSON, ChallengeToken: string): TJSONObject; AttestationObject, ClientDataJSON, ChallengeToken: string): TJSONObject;
var var
affectedRows: Int64; affectedRows: Int64;
...@@ -424,7 +434,7 @@ begin ...@@ -424,7 +434,7 @@ begin
// 1. Verify challenge token // 1. Verify challenge token
if not VerifyChallengeToken(ChallengeToken, if not VerifyChallengeToken(ChallengeToken,
'reg-' + normalizedPhone, challengeB64) then 'reg-' + normalizedPhone + '-' + LowerCase(Trim(DeviceType)), challengeB64) then
begin begin
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'Invalid or expired registration challenge.'); Result.AddPair('message', 'Invalid or expired registration challenge.');
...@@ -547,7 +557,7 @@ begin ...@@ -547,7 +557,7 @@ begin
if ctx <> nil then if ctx <> nil then
userAgent := ctx.Request.Headers.Get('User-Agent'); userAgent := ctx.Request.Headers.Get('User-Agent');
affectedRows := FDeviceStore.ActivateWebAuthnDevice(Trim(CredentialId), userAgent, pubKeyX, pubKeyY, pubKeyAlg, Integer(signCount), normalizedPhone); affectedRows := FDeviceStore.ActivateWebAuthnDevice(Trim(CredentialId), userAgent, pubKeyX, pubKeyY, pubKeyAlg, Integer(signCount), normalizedPhone, LowerCase(Trim(DeviceType)));
if affectedRows = 0 then if affectedRows = 0 then
begin begin
...@@ -998,7 +1008,7 @@ end; ...@@ -998,7 +1008,7 @@ end;
// CompleteRegistrationSimple — client-generated key, no crypto verification // CompleteRegistrationSimple — client-generated key, no crypto verification
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function TAuthService.CompleteRegistrationSimple(const PhoneNumber, DeviceKey, function TAuthService.CompleteRegistrationSimple(const PhoneNumber, DeviceType, DeviceKey,
ChallengeToken: string): TJSONObject; ChallengeToken: string): TJSONObject;
var var
affectedRows: Int64; affectedRows: Int64;
...@@ -1021,7 +1031,7 @@ begin ...@@ -1021,7 +1031,7 @@ begin
end; end;
if not VerifyChallengeToken(ChallengeToken, if not VerifyChallengeToken(ChallengeToken,
'reg-' + normalizedPhone, challengeB64) then 'reg-' + normalizedPhone + '-' + LowerCase(Trim(DeviceType)), challengeB64) then
begin begin
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'Invalid or expired registration challenge.'); Result.AddPair('message', 'Invalid or expired registration challenge.');
...@@ -1040,7 +1050,7 @@ begin ...@@ -1040,7 +1050,7 @@ begin
if ctx <> nil then if ctx <> nil then
userAgent := ctx.Request.Headers.Get('User-Agent'); userAgent := ctx.Request.Headers.Get('User-Agent');
affectedRows := FDeviceStore.ActivateSimpleDevice(Trim(DeviceKey), userAgent, normalizedPhone); affectedRows := FDeviceStore.ActivateSimpleDevice(Trim(DeviceKey), userAgent, normalizedPhone, LowerCase(Trim(DeviceType)));
if affectedRows = 0 then if affectedRows = 0 then
begin begin
......
...@@ -18,6 +18,19 @@ object FViewDeviceRegistration: TFViewDeviceRegistration ...@@ -18,6 +18,19 @@ object FViewDeviceRegistration: TFViewDeviceRegistration
TextHint = '(303) 555-1234' TextHint = '(303) 555-1234'
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
end end
object cmbDeviceType: TWebComboBox
Left = 240
Top = 110
Width = 177
Height = 25
ElementID = 'view.devicereg.devicetype'
ItemIndex = -1
Items.Strings = (
'iPhone'
'Android'
'Browser'
'Other')
end
object btnRegister: TWebButton object btnRegister: TWebButton
Left = 240 Left = 240
Top = 163 Top = 163
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<p class="text-muted small mb-3"> <p class="text-muted small mb-3">
This browser has not been registered for emiMobile access. This browser has not been registered for emiMobile access.
Enter the phone number your administrator registered for this device, Enter the phone number and select the device type your administrator registered for this device,
then click <strong>Send Code</strong>. then click <strong>Send Code</strong>.
</p> </p>
...@@ -46,6 +46,11 @@ ...@@ -46,6 +46,11 @@
autofocus> autofocus>
</div> </div>
<div class="mb-3">
<label class="form-label small text-muted" for="view.devicereg.devicetype">Device type</label>
<select id="view.devicereg.devicetype" class="form-select" aria-label="Device type"></select>
</div>
<div id="view.devicereg.verification" class="mb-3 d-none"> <div id="view.devicereg.verification" class="mb-3 d-none">
<label class="form-label small text-muted">Verification code</label> <label class="form-label small text-muted">Verification code</label>
<input id="view.devicereg.edtverificationcode" <input id="view.devicereg.edtverificationcode"
......
...@@ -11,6 +11,7 @@ uses ...@@ -11,6 +11,7 @@ uses
type type
TFViewDeviceRegistration = class(TWebForm) TFViewDeviceRegistration = class(TWebForm)
edtPhoneNumber: TWebEdit; edtPhoneNumber: TWebEdit;
cmbDeviceType: TWebComboBox;
btnRegister: TWebButton; btnRegister: TWebButton;
pnlVerification: TWebPanel; pnlVerification: TWebPanel;
edtVerificationCode: TWebEdit; edtVerificationCode: TWebEdit;
...@@ -25,6 +26,7 @@ type ...@@ -25,6 +26,7 @@ type
procedure WebFormCreate(Sender: TObject); procedure WebFormCreate(Sender: TObject);
private private
FRegistrationProc: TSuccessProc; FRegistrationProc: TSuccessProc;
FDeviceType: string;
procedure ShowNotification(const AMsg: string; AIsError: Boolean = True); procedure ShowNotification(const AMsg: string; AIsError: Boolean = True);
procedure HideNotification; procedure HideNotification;
procedure SetBusy(ABusy: Boolean); procedure SetBusy(ABusy: Boolean);
...@@ -90,6 +92,7 @@ end; ...@@ -90,6 +92,7 @@ end;
procedure TFViewDeviceRegistration.SetBusy(ABusy: Boolean); procedure TFViewDeviceRegistration.SetBusy(ABusy: Boolean);
begin begin
cmbDeviceType.Enabled := (not ABusy) and edtPhoneNumber.Enabled;
asm asm
var btn = document.getElementById('view.devicereg.btnregister'); var btn = document.getElementById('view.devicereg.btnregister');
if (btn) { if (btn) {
...@@ -118,6 +121,7 @@ var ...@@ -118,6 +121,7 @@ var
begin begin
SetBusy(False); SetBusy(False);
edtPhoneNumber.Enabled := False; edtPhoneNumber.Enabled := False;
cmbDeviceType.Enabled := False;
pnlVerification.ElementHandle.classList.remove('d-none'); pnlVerification.ElementHandle.classList.remove('d-none');
edtVerificationCode.SetFocus; edtVerificationCode.SetFocus;
ShowNotification('Verification code sent. Enter the six-digit code to continue.', False); ShowNotification('Verification code sent. Enter the six-digit code to continue.', False);
...@@ -137,10 +141,17 @@ begin ...@@ -137,10 +141,17 @@ begin
Exit; Exit;
end; end;
if cmbDeviceType.ItemIndex < 0 then
begin
ShowNotification('Please select the device type your administrator registered.');
Exit;
end;
FDeviceType := LowerCase(cmbDeviceType.Text);
SetBusy(True); SetBusy(True);
HideNotification; HideNotification;
AuthService.BeginRegistration(phoneNumber, @OnCodeSent, @OnCodeError); AuthService.BeginRegistration(phoneNumber, FDeviceType, @OnCodeSent, @OnCodeError);
end; end;
procedure TFViewDeviceRegistration.btnVerifyCodeClick(Sender: TObject); procedure TFViewDeviceRegistration.btnVerifyCodeClick(Sender: TObject);
...@@ -178,7 +189,7 @@ begin ...@@ -178,7 +189,7 @@ begin
SetVerifyBusy(True); SetVerifyBusy(True);
HideNotification; HideNotification;
AuthService.VerifyRegistrationCode(phoneNumber, verificationCode, AuthService.VerifyRegistrationCode(phoneNumber, FDeviceType, verificationCode,
@OnVerified, @OnVerifyError); @OnVerified, @OnVerifyError);
end; end;
...@@ -201,7 +212,7 @@ var ...@@ -201,7 +212,7 @@ var
procedure OnCredential(ACredentialId, AAttestationObject, AClientDataJSON: string); procedure OnCredential(ACredentialId, AAttestationObject, AClientDataJSON: string);
begin begin
AuthService.CompleteRegistration( AuthService.CompleteRegistration(
phoneNumber, ACredentialId, AAttestationObject, AClientDataJSON, challengeToken, phoneNumber, FDeviceType, ACredentialId, AAttestationObject, AClientDataJSON, challengeToken,
@OnCompleteOK, @OnCompleteError @OnCompleteOK, @OnCompleteError
); );
end; end;
...@@ -295,7 +306,7 @@ begin ...@@ -295,7 +306,7 @@ begin
end; end;
AuthService.CompleteRegistrationSimple( AuthService.CompleteRegistrationSimple(
phoneNumber, deviceKey, challengeToken, phoneNumber, FDeviceType, deviceKey, challengeToken,
@OnCompleteOK, @OnCompleteError @OnCompleteOK, @OnCompleteError
); );
end; end;
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Client=WC Client=WC
[Settings] [Settings]
LogFileNum=184 LogFileNum=185
webClientVersion=0.9.4.9 webClientVersion=0.9.4.9
MemoLogLevel=3 MemoLogLevel=3
FileLogLevel=4 FileLogLevel=4
......
emiMobile_BFD @ 13d204d6
Subproject commit 13d204d656b7ccc69148d12d46d95e642e8b0a44
unit DTOGeneratorUtils;
interface
uses
SysUtils, Classes, Uni, UniProvider, DB, System.JSON, Pkg.Json.DTO;
procedure GenerateDelphiJDOForTable(UniConnection: TUniConnection; const TableName: string; S: TStrings);
function GenerateJsonDTOFromJson(const JsonStr, ClassName: string): string;
function JsonTypeToDelphiType(const Value: TJSONValue): string;
function SqlTypeToDelphiType(const SqlType: string): string;
procedure GenerateJDOsForTables(UniConn: TUniConnection);
procedure JsonToDTO(JsonStr:string);
implementation
function JsonTypeToDelphiType(const Value: TJSONValue): string;
begin
if Value is TJSONNumber then
begin
if Pos('.', Value.Value) > 0 then
Result := 'Double'
else
Result := 'Integer'
end
else if Value is TJSONString then
Result := 'string'
else if Value is TJSONBool then
Result := 'Boolean'
else if Value is TJSONArray then
Result := 'TArray<string>' // Or further analysis could detect array of objects/numbers/etc
else if Value is TJSONObject then
Result := 'TObject' // Extend for nested DTOs if needed
else if Value is TJSONNull then
Result := 'Variant'
else
Result := 'string'
end;
function SqlTypeToDelphiType(const SqlType: string): string;
begin
if SqlType.Contains('int') then
Result := 'Integer'
else if SqlType.Contains('char') or SqlType.Contains('text') then
Result := 'string'
else if SqlType.Contains('bool') then
Result := 'Boolean'
else if SqlType.Contains('date') or SqlType.Contains('time') then
Result := 'TDateTime'
else if SqlType.Contains('double') or SqlType.Contains('numeric') or SqlType.Contains('real') then
Result := 'Double'
else
Result := 'string'; // fallback
end;
//utility
function PascalCase(const S: string): string;
var
i: Integer;
NextUpper: Boolean;
begin
Result := '';
NextUpper := True;
for i := 1 to Length(S) do
begin
if S[i] in ['a'..'z', 'A'..'Z', '0'..'9'] then
begin
if NextUpper then
Result := Result + UpCase(S[i])
else
Result := Result + LowerCase(S[i]);
NextUpper := False;
end
else
NextUpper := True;
end;
end;
procedure GenerateDelphiJDOForTable(UniConnection: TUniConnection; const TableName: string; S: TStrings);
var
Q: TUniQuery;
FieldName, FieldType, DelphiType: string;
begin
Q := TUniQuery.Create(nil);
try
Q.Connection := UniConnection;
Q.SQL.Text := Format(
'SELECT column_name, data_type ' +
'FROM information_schema.columns ' +
'WHERE table_name = %s ORDER BY ordinal_position', [QuotedStr(TableName)]);
Q.Open;
S.Add('type');
S.Add(' T' + TableName + 'JDO = class');
S.Add(' public');
while not Q.Eof do
begin
FieldName := Q.FieldByName('column_name').AsString;
FieldType := Q.FieldByName('data_type').AsString;
DelphiType := SqlTypeToDelphiType(FieldType);
// PascalCase the field
FieldName := StringReplace(FieldName, '_', '', [rfReplaceAll]);
FieldName[1] := UpCase(FieldName[1]);
S.Add(Format(' %s: %s;', [FieldName, DelphiType]));
Q.Next;
end;
S.Add(' end;');
S.Add('');
finally
Q.Free;
end;
end;
procedure GenerateJDOsForTables(UniConn: TUniConnection);
var
TableList: TArray<string>;
Table: string;
OutCode: TStringList;
begin
TableList := ['customer', 'orders', 'product']; // put your schema table names here
OutCode := TStringList.Create;
try
for Table in TableList do
begin
OutCode.Clear();
GenerateDelphiJDOForTable(UniConn, Table, OutCode);
// Output to console, file, or in-memory
OutCode.SaveToFile(Table+'JDOs.pas');
//Writeln(OutCode.Text);
end;
finally
OutCode.Free;
UniConn.Free;
end;
end;
function GenerateJsonDTOFromJson(const JsonStr, ClassName: string): string;
var
Json: TJSONObject;
JPair: TJSONPair;
FieldType, PubPropName, PriFieldName: string;
Code: TStringList;
begin
Code := TStringList.Create;
try
Json := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject;
if not Assigned(Json) then
raise Exception.Create('Invalid/Unsupported JSON root object.');
Code.Add('type');
Code.Add(' T' + PascalCase(ClassName) + 'DTO = class(TJsonDTO)');
Code.Add(' private');
// Private fields
for JPair in Json do
begin
FieldType := JsonTypeToDelphiType(JPair.JsonValue);
PriFieldName := 'F' + PascalCase(JPair.JsonString.Value);
Code.Add(Format(' %s: %s;', [PriFieldName, FieldType]));
end;
Code.Add(' published');
// Published properties (JSON mapping)
for JPair in Json do
begin
FieldType := JsonTypeToDelphiType(JPair.JsonValue);
PubPropName := JPair.JsonString.Value; // Use original JSON key for property
PriFieldName := 'F' + PascalCase(PubPropName);
Code.Add(Format(' property %s: %s read %s write %s;', [
PubPropName, FieldType, PriFieldName, PriFieldName
]));
end;
Code.Add(' end;');
Result := Code.Text;
Json.Free;
finally
Code.Free;
end;
end;
procedure JsonToDTO(JsonStr:string);
var
ClassName: string;
ClassSource: string;
OutCode: TStringList;
begin
OutCode := TStringList.Create;
try
ClassName:='Customer';
JsonStr := '{ "id": 1, "name": "Alice", "isActive": true, "score": 42.1, "tags": ["one", "two"] }';
ClassSource := GenerateJsonDTOFromJson(JsonStr, ClassName);
Writeln(ClassSource);
OutCode.SaveToFile(ClassName+'JDOs.pas');
//Writeln(OutCode.Text);
finally
OutCode.Free
end;
end;
end.
# Development tools
DTOGeneratorUtils.pas is a retained Delphi source-generation utility. It is not part of the emiMobile server build. It uses UniDAC and Pkg.Json.DTO; this is reference tooling, not a standalone executable project.
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