// Implementation of Auth Serice that will eventually retrieve login information
// from the auth database.

unit Auth.ServiceImpl;

interface

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

type
  [ServiceImplementation]
  TAuthService = class(TInterfacedObject, IAuthService)
  strict private
    authDB: TAuthDatabase;
    function GetQuery: TUniQuery;
  private
    userName: string;
    userFullName: string;
    userId: string;
    userPerspectiveID: string;
    userQBID: string;
    userAccessType: string;
    userEmail: string;
    userStatus: string;
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    property Query: TUniQuery read GetQuery;
    function CheckUser(const user, password: string): Integer;
  public
    function Login(const user, password: string): string;
    function VerifyVersion(ClientVersion: string): TJSONObject;
  end;

implementation

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

{ TLoginService }

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.VerifyVersion(ClientVersion: string): TJSONObject;
var
  iniFile: TIniFile;
  webClientVersion: string;
begin
  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
      Result.AddPair('error', 'webClientVersion is not configured.');
      Exit;
    end;
    if clientVersion <> webClientVersion then
    begin
      Result.AddPair('error',
        'Your browser is running an old version of the app.' + sLineBreak +
        'Please click below to reload.');
    end;
  finally
    iniFile.Free;
  end;
end;


function TAuthService.Login(const user, password: string): string;
// Login verification: currently checks if logins are the same or if the user
// is admin, checks for admin password. Eventually will do a database lookup
// instead. If the user has a JWT token they do not need to login.
// Potential error occuring when logging in after you have already logged in
// webcharms.
var
  userState: Integer;
  JWT: TJWT;
begin
  Logger.Log(1, Format( 'AuthService.Login - User: "%s"', [User]));
  userState := CheckUser( user, password );

  if userState = 0 then
    raise EXDataHttpUnauthorized.Create('Invalid username or password');
  if userState = 1 then
    raise EXDataHttpUnauthorized.Create('User does not exist!');
  if userState = 2 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_id', userId);
    JWT.Claims.SetClaimOfType<string>('user_perspective_id', userPerspectiveID);
    JWT.Claims.SetClaimOfType<string>('user_status', userStatus);
    JWT.Claims.SetClaimOfType<string>('user_email', userEmail);
    JWT.Claims.SetClaimOfType<string>('user_qb_id', userQBID);
    JWT.Claims.SetClaimOfType<string>('user_access_type', userAccessType);

    Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, JWT);
  finally
    JWT.Free;
  end;
end;

function TAuthService.CheckUser(const user, password: string): Integer;
var
  userStr: string;
  SQL: string;
  date_created: string;
  checkString: string;
begin
  //authDB := TAuthDatabase.Create(nil);
  Result := 0;
  //Logger.Log( 3, Format('AuthService.CheckUser - User: "%s"', [user]) );
  SQL := 'select * from users where USER_NAME = ' + QuotedStr(user);
  DoQuery(authDB.uq, SQL);
  if authDB.uq.IsEmpty then
  begin
      Result := 1;   //user does not exist, replace this with 0 for more security
  end
  else if ( authDB.uq.FieldByName('STATUS').AsString <> 'ACTIVE' ) then
    Result := 2 // user is not active
  else
  begin
    //date_created := authDB.uq.FieldByName('date_created').AsString;
    //checkString := THashSHA2.GetHashString(date_created + password, THashSHA2.TSHA2Version.SHA512).ToUpper;
    if password = authDB.uq.FieldByName('PASSWORD').AsString then
    begin
      userName := user;
      userFullName:= authDB.uq.FieldByName('NAME').AsString;;
      userId := authDB.uq.FieldByName('USER_ID').AsString;
      userStatus := authDB.uq.FieldByName('STATUS').AsString;
      userPerspectiveID := authDB.uq.FieldByName('PERSPECTIVE_ID').AsString;
      userEmail := authDB.uq.FieldByName('EMAIL').AsString;
      userQBID := authDB.uq.FieldByName('QB_ID').AsString;
      userAccessType := authDB.uq.FieldByName('ACCESS_TYPE').AsString;

      //Logger.Log( 3, Format('AuthDB.SetLoginAuditEntry: "%s"', [user]) );
      //AuthDB.SetLoginAuditEntry( userStr );
      Result := 3; // Succcess
    end
    else
      Result := 0; // invalid password
  end;
end;

initialization
  RegisterServiceType(TAuthService);
end.
