unit Auth.ServiceImpl;

interface

uses
  System.SysUtils,
  System.Classes,
  System.JSON,
  System.IniFiles,
  System.DateUtils,
  System.Hash,
  Data.DB,
  XData.Service.Common,
  XData.Sys.Exceptions,
  XData.Server.Module,
  Aurelius.Global.Utils,
  Bcl.JOSE.Core.JWT,
  Bcl.JOSE.Core.Builder,
  Auth.Service,
  Auth.Database,
  Common.Logging,
  Common.Config,
  uLibrary;

type
  [ServiceImplementation]
  TAuthService = class(TInterfacedObject, IAuthService)
  strict private
    AuthDb: TAuthDatabase;
    function GetWebClientVersion: string;
  public
    // Note: CHANGED - Login now takes a plain text password (kgOrders style)
    function Login(const username, password, clientVersion: string): string;
    function VerifyVersion(clientVersion: string): TJSONObject;
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  end;

implementation

procedure TAuthService.AfterConstruction;
begin
  inherited;
  try
    AuthDb := TAuthDatabase.Create(nil);
    Logger.Log(5, 'TAuthService.AfterConstruction - AuthDb created');
  except
    on E: Exception do
    begin
      Logger.Log(1, 'Error creating AuthDb: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Unable to create Auth database.');
    end;
  end;
end;

procedure TAuthService.BeforeDestruction;
begin
  AuthDb.Free;
  inherited;
  Logger.Log(5, 'TAuthService.BeforeDestruction - AuthDb freed');
end;

function TAuthService.GetWebClientVersion: string;
var
  iniFile: TIniFile;
begin
  iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
  try
    Result := iniFile.ReadString('Settings', 'webClientVersion', '');
  finally
    iniFile.Free;
  end;
end;

function TAuthService.VerifyVersion(clientVersion: string): TJSONObject;
var
  webClientVersion: string;
begin
  Logger.Log(5, 'TAuthService.VerifyVersion(clientVersion: ' + clientVersion + ')');

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

  webClientVersion := GetWebClientVersion;
  Result.AddPair('webClientVersion', webClientVersion);

  if webClientVersion = '' then
  begin
    Result.AddPair('error', 'webClientVersion is not configured.');
    Exit;
  end;

  if clientVersion <> webClientVersion then
  begin
    Logger.Log(1, 'webApp version mismatch client ver: ' + clientVersion + ' required ver: ' + webClientVersion);
    Result.AddPair('error',
      'webApp version mismatch' + sLineBreak +
      '  Client version: ' + clientVersion + sLineBreak +
      '  Server version: ' + webClientVersion + sLineBreak +
      'Please clear cache and reload.');
  end;
end;

function TAuthService.Login(const username, password, clientVersion: string): string;
var
  sql: string;
  storedHash: string;
  passwordHash: string;
  webClientVersion: string;
  jwt: TJWT;
begin
  Logger.Log(3, Format('AuthService.Login - User: "%s"', [username]));

  try
    sql := 'select user_id, user_name, user_password from users where user_name = ' + QuotedStr(username) + ' limit 1';
    DoQuery(AuthDb.uqUser, sql);
  except
    on E: Exception do
    begin
      Logger.Log(1, 'Login failed due to database error: ' + E.Message);
      raise EXDataHttpException.Create(500, 'Login failed: Unable to connect to the database.');
    end;
  end;

  if AuthDb.uqUser.IsEmpty then
  begin
    Logger.Log(2, 'Login Error: Invalid username or password');
    raise EXDataHttpUnauthorized.Create('Invalid username or password');
  end;

  // ADD THIS DELETE BEFORE DEPLOY
  passwordHash := password.ToUpper;
  storedHash := AuthDb.uqUser.FieldByName('user_password').AsString.ToUpper;
  // STOP HERE DELETE BEFORE DEPLOY

  // REMOVE THIS DELETE BEFORE DEPLOY
  // passwordHash := THashSHA2.GetHashString(username + password, THashSHA2.TSHA2Version.SHA256).ToUpper;
  // storedHash := AuthDb.uqUser.FieldByName('user_password').AsString.ToUpper;
  // STOP HERE DELETE BEFORE DEPLOY

  if storedHash <> passwordHash then
  begin
    Logger.Log(2, 'Login Error: Invalid username or password');
    raise EXDataHttpUnauthorized.Create('Invalid username or password');
  end;

  webClientVersion := GetWebClientVersion;

  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<Integer>('user_id', AuthDb.uqUser.FieldByName('user_id').AsInteger);
    jwt.Claims.SetClaimOfType<string>('user_name', AuthDb.uqUser.FieldByName('user_name').AsString);

    jwt.Claims.SetClaimOfType<string>('client_version', clientVersion);
    jwt.Claims.SetClaimOfType<string>('server_version', webClientVersion);

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

initialization
  RegisterServiceType(TAuthService);

end.
