unit Auth.ServiceImpl;

interface

uses
  XData.Service.Common,
  XData.Server.Module,
  Auth.Service,
  Auth.Database,
  Uni, Data.DB, System.JSON, System.SysUtils, System.IOUtils, IniFiles;

type
  [ServiceImplementation]
  TAuthService = class(TInterfacedObject, IAuthService)
  strict private
    authDB: TAuthDatabase;
    function GetQuery: TUniQuery;
  private
    userName: string;
    userFullName: string;
    userAgency: string;
    userBadge: string;
    userId: string;
    userPersonnelId: string;
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    function VerifyVersion(ClientVersion: string): TJSONObject;
    property Query: TUniQuery read GetQuery;
    function CheckUser(const User, Password, Agency: string): Integer;
    function Decrypt(inStr, keyStr: AnsiString): AnsiString;
  public
    function Login(const User, Password, Agency: string): string;
    function GetAgencieslist(): TAgenciesList;
    function GetAgencyConfiglist: TAgencyConfigList;
  end;

implementation

uses
  System.DateUtils,
  System.Generics.Collections,
  Bcl.JOSE.Core.Builder,
  Bcl.JOSE.Core.JWT,
  Aurelius.Global.Utils,
  XData.Sys.Exceptions,
  Common.Logging,
  Common.Config;

{ TAuthService }

procedure TAuthService.AfterConstruction;
begin
  inherited;
  authDB := TAuthDatabase.Create(nil);
end;

procedure TAuthService.BeforeDestruction;
begin
  authDB.Free;
  inherited;
end;

function TAuthService.GetQuery: TUniQuery;
begin
  Result := authDB.uq;
end;

function TAuthService.GetAgenciesList: TAgenciesList;
var
  agency: TAgencyItem;
begin
  Logger.Log(2, 'TAuthService.GetAgenciesList - call');

  if authDB.ucBooking.Connected then
  begin
    authDB.uqBooking.Close;
    authDB.uqBooking.SQL.Text := 'select * from agencies order by agency';
    authDB.uqBooking.Open;

    Result := TAgenciesList.Create;
    Result.data := TList<TAgencyItem>.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
    while not authDB.uqBooking.Eof do
    begin
      agency := TAgencyItem.Create(authDB.uqBooking.FieldByName('agency').AsString);
      TXDataOperationContext.Current.Handler.ManagedObjects.Add(agency);
      Result.Data.Add(agency);
      authDB.uqBooking.Next;
    end;
    authDB.uqBooking.Close;
    Result.count := Result.data.count;
    Result.returned := Result.data.count;
    Logger.Log( 2, 'GetAgenciesList - Count: ' + IntToStr(Result.Count) + ' Returned: ' + IntToStr(Result.Returned) );
  end
  else
  begin
    Logger.Log( 2, 'TAuthService.GetAgenciesList - Error: connecting to CPS database!' );
    raise EXDataHttpException.Create('Error connecting to CPS database!');
  end;
end;

function TAuthService.GetAgencyConfigList: TAgencyConfigList;
var
  agencyConfig: TAgencyConfigItem;
  sql: string;
begin
  Logger.Log(2, 'AuthService.GetAgencyConfigList - call');

  authDB.uqBooking.Close;
  sql := 'select agency_id, agency, agency_name from agencyconfig ' +
         'where active = ''Y'' order by agency';
  authDB.uqBooking.SQL.Text := sql;
  authDB.uqBooking.Open;

  Result := TAgencyConfigList.Create;
  Result.data := TList<TAgencyConfigItem>.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
  while not authDB.uqBooking.Eof do
  begin
    agencyConfig := TAgencyConfigItem.Create;
    TXDataOperationContext.Current.Handler.ManagedObjects.Add(agencyConfig);
    Result.data.Add(agencyConfig);
    agencyConfig.id := authDB.uqBooking.FieldByName('agency_id').AsInteger;
    agencyConfig.agency := authDB.uqBooking.FieldByName('agency').AsString;
    agencyConfig.name := authDB.uqBooking.FieldByName('agency_name').AsString;
    authDB.uqBooking.Next;
  end;
  authDB.uqBooking.Close;

  Result.count := Result.data.Count;
  Result.returned := Result.data.Count;
  Logger.Log(2, 'GetAgencyConfigList - ' + IntToStr(Result.Count));
end;


function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject;
var
  iniFile: TIniFile;
  webClientVersion: string;
begin
  Logger.Log(3, 'AuthService.VerifyVersion called');

  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
  try
    webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
    Result.AddPair('webClientVersion', webClientVersion);

    if webClientVersion = '' then
    begin
      Logger.Log(2, 'AuthService.VerifyVersion: webClientVersion not configured');
      Result.AddPair('error', 'webClientVersion is not configured.');
      Exit;
    end;

    if clientVersion <> webClientVersion then
    begin
      Logger.Log(2, 'AuthService.VerifyVersion: client version mismatch');
      Result.AddPair('error',
        'Your browser is running an old version of the app.' + sLineBreak +
        'Please click below to reload.');
    end
    else
      Logger.Log(3, 'AuthService.VerifyVersion: version check passed');
  finally
    iniFile.Free;
  end;
end;


function TAuthService.Login(const User, Password, Agency: string): string;
var
  userState: Integer;
  JWT: TJWT;
begin
  Logger.Log(1, Format('AuthService.Login - User: "%s" Agency: "%s"', [User, Agency]));
  userState := CheckUser(User, Password, Agency);

  if userState = 0 then
    raise EXDataHttpUnauthorized.Create('Invalid user or password');
  if userState = 1 then
    raise EXDataHttpUnauthorized.Create('User not active!');

  JWT := TJWT.Create;
  try
    JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
    JWT.Claims.IssuedAt := Now;
    JWT.Claims.Expiration := IncHour(Now, 24);
    JWT.Claims.SetClaimOfType<string>('user_name', userName);
    JWT.Claims.SetClaimOfType<string>('user_fullname', userFullName);
    JWT.Claims.SetClaimOfType<string>('user_agency', userAgency);
    JWT.Claims.SetClaimOfType<string>('user_badge', userBadge);
    JWT.Claims.SetClaimOfType<string>('user_id', userId);
    JWT.Claims.SetClaimOfType<string>('user_personnelid', userPersonnelId);
    Result := TJOSE.SHA256CompactToken(ServerConfig.jwtTokenSecret, JWT);
  finally
    JWT.Free;
  end;
end;

function TAuthService.CheckUser(const User, Password, Agency: string): Integer;
var
  userStr: string;
  sqlStr: string;
  decryptedPassword: AnsiString;
  passwordKey: AnsiString;
begin
  Logger.Log(3, Format('LoginService.CheckUser - User: "%s" Agency: "%s"', [User, Agency]));
  passwordKey := 'wx3cFo$kIf2jrk(gOmvi7uvPfk*iorE8@kfm+nvR6jfh=swDqalpokSjf';

  sqlStr := 'select u.* from users@cps_link u ';
  sqlStr := sqlStr + 'where upper(user_name) = ' + QuotedStr(UpperCase(Trim(User))) + ' ';
  sqlStr := sqlStr + 'and u.dept = ' + QuotedStr(UpperCase(Trim(Agency)));
  authDB.uqBooking.SQL.Text := sqlStr;
  Logger.Log(4, Format('LoginService.CheckUser - Query: "%s"', [sqlStr]));
  authDB.uqBooking.Open;

  if authDB.uqBooking.IsEmpty then
    Result := 0
  else
  begin
    if authDB.uqBooking.FieldByName('active').AsString = 'F' then
      Result := 1
    else
    begin
      decryptedPassword := Uppercase(Trim(Decrypt(authDB.uqBooking.FieldByName('password').AsString, passwordKey)));
      if decryptedPassword = Uppercase(Trim(Password)) then
      begin
        userName        := authDB.uqBooking.FieldByName('user_name').AsString;
        userFullName    := authDB.uqBooking.FieldByName('firstname').AsString + ' ' + authDB.uqBooking.FieldByName('lastname').AsString;
        userAgency      := authDB.uqBooking.FieldByName('dept').AsString;
        userBadge       := authDB.uqBooking.FieldByName('badgenum').AsString;
        userId          := authDB.uqBooking.FieldByName('userid').AsString;
        userPersonnelId := authDB.uqBooking.FieldByName('personnelid').AsString;

        userStr := '?username=' + userName;
        userStr := userStr + '&fullname=' + userFullName;
        userStr := userStr + '&agency=' + userAgency;
        userStr := userStr + '&userid=' + userId;
        userStr := userStr + '&personnelid=' + userPersonnelId;
        authDB.SetLoginAuditEntry(userStr);

        Result := 2;
      end
      else
        Result := 0;
    end;
  end;
end;

function TAuthService.Decrypt(inStr, keyStr: AnsiString): AnsiString;
var
  outStr: AnsiString;
  k, i: integer;
  tempKeyStr: AnsiString;
begin
  k := Integer(inStr[1]);
  tempKeyStr := keyStr;
  while Length(tempKeyStr) < 256 do
    tempKeyStr := tempKeyStr + tempKeyStr;

  for i := 1 to Length(inStr) - 1 do
  begin
    k := (k + i) mod 256;
    outStr := outStr + AnsiChar(Integer(inStr[i+1]) xor Integer(tempKeyStr[k+1]));
  end;
  Result := Trim(outStr);
end;

initialization
  RegisterServiceType(TAuthService);

end.

