Hi, as far as I can see there's the demo application and the sphinx-user-guide.pdf included in TMSSphinx. Is there additional documentation or online courses available or do we need to "explore" that stuff the hard way with reading the source code and exploring the forums? I would like to use bcrypt as password hashing algorithm for example because I have an older app which I want to migrate / upgrade to delphi and sphinx and need to reuse existing hashes and not force the users to change their passwords... (I know how to bcrypt but I want to know how to integrate it in Sphinx)
And the online documentation (which has the same content of PDF, but different format of course): Overview | TMS Sphinx documentation
There is this session about TMS Sphinx from last TMS Training Days 2023:
Well, at some point you will have to do that. There is no way we can cover every an each possible thing you can do with TMS Sphinx (or any other product) in our courses and documentation.
If you have existing password, probably just changing the hash won't solve you problem, because you have existing user tables, that you want to use instead of the existing ones Sphinx creates, right? Because Sphinx checks the password agains a hash saved in its own table. In this case, one alternative is you create your own TUserManager implementation, or inherit from the existing one, and override the methods you need to make it work.
It's low-level, but here is an example that completely removes Sphinx own tables and use custom ones.
unit B2B.UserManager;
interface
uses
SysUtils, Generics.Collections, Bcl.Validation.Interfaces, Hash,
Aurelius.Engine.ObjectManager, Aurelius.Drivers.Interfaces,
Sphinx.UserManager, Sphinx.UserManagerFactory, Sphinx.Entities, Sphinx.CoreOptions,
Sphinx.UserValidator, Sphinx.PasswordValidator,
B2B.Entities;
type
TMyUserManagerFactory = class(TInterfacedObject, IUserManagerFactory)
public
function CreateUserManager(AConfig: ICoreOptions; AManager: TObjectManager; AOwnsManager: boolean): IUserManager;
end;
TMyUserManager = class(TInterfacedObject, IUserManager)
strict private
FConfig: ICoreOptions;
FSphinxManager: TObjectManager;
FManager: TObjectManager;
FOwnsManager: Boolean;
FUserValidators: TList<IUserValidator>;
FPasswordValidators: TList<IPasswordValidator>;
procedure AddOwnership(Obj: TObject);
function Manager: TObjectManager;
strict private
procedure UsuarioToUser(Source: TUsuarios; Dest: TUser);
public
constructor Create(AConfig: ICoreOptions; AManager: TObjectManager; AOwnsManager: Boolean);
destructor Destroy; override;
public
procedure CreateUser(User: TUser); overload;
procedure CreateUser(User: TUser; const Password: string); overload;
procedure UpdateUser(User: TUser);
procedure DeleteUser(User: TUser);
function FindById(const Id: string): TUser;
function FindByName(const UserName: string): TUser;
function FindByEmail(const Email: string): TUser;
function FindByPhoneNumber(const PhoneNumber: string): TUser;
function CheckPassword(User: TUser; const Password: string): Boolean;
function GenerateEmailConfirmationToken(User: TUser): string;
procedure ConfirmEmail(User: TUser; const Token: string); overload;
function GeneratePhoneNumberConfirmationToken(User: TUser): string;
procedure ConfirmPhoneNumber(User: TUser; const Token: string); overload;
function GeneratePasswordResetToken(User: TUser): string;
function GenerateChangePhoneNumberToken(User: TUser; const NewPhoneNumber: string): string;
procedure ChangePhoneNumber(User: TUser; const Token, NewPhoneNumber: string);
function BeginResetPassword(User: TUser; const Token: string): string;
function GeneratePasswordChangeToken(User: TUser): string;
procedure ChangePassword(User: TUser; const Token, NewPassword: string);
function GenerateChangeEmailToken(User: TUser; const NewEmail: string): string;
procedure ChangeEmail(User: TUser; const Token, NewEmail: string);
function IsLockedOut(User: TUser): Boolean;
procedure AccessFailed(User: TUser);
procedure ResetAccessFailedCount(User: TUser);
procedure AddPassword(User: TUser; const Password: string);
function GenerateUserToken(User: TUser; const TokenProvider: string; const Purpose: string): string;
function VerifyUserToken(User: TUser; const TokenProvider: string; const Purpose: string; const Token: string): Boolean;
procedure SetAuthenticationToken(User: TUser; const LoginProvider, TokenName, TokenValue: string);
function GetAuthenticationToken(User: TUser; const LoginProvider, TokenName: string): string;
procedure RemoveAuthenticationToken(User: TUser; const LoginProvider, TokenName: string);
procedure ValidateUser(User: TUser);
procedure ValidatePassword(User: TUser; const Password: string);
function CreateUserInstance: TUser;
function CreateRoleInstance: TRole;
end;
implementation
uses
Aurelius.Criteria.Linq,
Sphinx.UserManager.Impl;
{ TMyUserManager }
procedure TMyUserManager.AccessFailed(User: TUser);
begin
end;
procedure TMyUserManager.AddOwnership(Obj: TObject);
begin
FSphinxManager.AddOwnership(Obj);
end;
procedure TMyUserManager.AddPassword(User: TUser; const Password: string);
begin
raise Exception.Create('AddPassword not supported');
end;
function TMyUserManager.BeginResetPassword(User: TUser; const Token: string): string;
begin
raise Exception.Create('BeginResetPassword not supported');
end;
procedure TMyUserManager.ChangeEmail(User: TUser; const Token, NewEmail: string);
begin
raise Exception.Create('ChangeEmail not supported');
end;
procedure TMyUserManager.ChangePassword(User: TUser; const Token, NewPassword: string);
begin
raise Exception.Create('ChangePassword not supported');
end;
procedure TMyUserManager.ChangePhoneNumber(User: TUser; const Token, NewPhoneNumber: string);
begin
raise Exception.Create('ChangePhoneNumber not supported');
end;
function TMyUserManager.CheckPassword(User: TUser; const Password: string): Boolean;
var
Hash: string;
begin
Hash := User.PasswordHash.AsUnicodeString;
if Hash = '' then
Exit(False);
Result := SameText(Hash, THashMD5.GetHashString(Password));
end;
procedure TMyUserManager.ConfirmEmail(User: TUser; const Token: string);
begin
raise Exception.Create('ConfirmEmail not supported');
end;
procedure TMyUserManager.ConfirmPhoneNumber(User: TUser; const Token: string);
begin
raise Exception.Create('ConfirmPhoneNumber not supported');
end;
constructor TMyUserManager.Create(AConfig: ICoreOptions; AManager: TObjectManager; AOwnsManager: Boolean);
begin
inherited Create;
FConfig := AConfig;
FSphinxManager := AManager;
FOwnsManager := AOwnsManager;
FUserValidators := TList<IUserValidator>.Create;
FPasswordValidators := TList<IPasswordValidator>.Create;
// Init user validators
FUserValidators.Add(TUserNameValidator.Create(FConfig.UserOptions));
FUserValidators.Add(TUserEmailValidator.Create(FConfig.UserOptions));
FUserValidators.Add(TUserPhoneNumberValidator.Create(FConfig.UserOptions));
FUserValidators.AddRange(FConfig.UserValidators);
// Init password validators
FPasswordValidators.Add(TDefaultPasswordValidator.Create(FConfig.PasswordOptions));
FPasswordValidators.AddRange(FConfig.PasswordValidators);
end;
function TMyUserManager.CreateRoleInstance: TRole;
begin
Result := FConfig.EntityFactory.CreateRole;
end;
procedure TMyUserManager.CreateUser(User: TUser; const Password: string);
begin
raise Exception.Create('CreateUser not supported');
end;
procedure TMyUserManager.CreateUser(User: TUser);
begin
raise Exception.Create('CreateUser not supported');
end;
function TMyUserManager.CreateUserInstance: TUser;
begin
Result := FConfig.EntityFactory.CreateUser;
end;
procedure TMyUserManager.DeleteUser(User: TUser);
begin
raise Exception.Create('DeleteUser not supported');
end;
destructor TMyUserManager.Destroy;
begin
if FOwnsManager then
FSphinxManager.Free;
FManager.Free;
FUserValidators.Free;
FPasswordValidators.Free;
inherited;
end;
function TMyUserManager.FindByEmail(const Email: string): TUser;
var
Usuario: TUsuarios;
begin
Usuario := Manager.Find<TUsuarios>
.Add(Linq['LOGIN'] = Uppercase(Trim(Email)))
.UniqueResult;
if Usuario <> nil then
begin
Result := TUser.Create;
AddOwnership(Result);
UsuarioToUser(Usuario, Result);
end
else
Result := nil;
end;
function TMyUserManager.FindById(const Id: string): TUser;
var
Usuario: TUsuarios;
begin
Usuario := Manager.Find<TUsuarios>
.Add(Linq['ID'] = Uppercase(Trim(Id)))
.UniqueResult;
if Usuario <> nil then
begin
Result := TUser.Create;
AddOwnership(Result);
UsuarioToUser(Usuario, Result);
end
else
Result := nil;
end;
function TMyUserManager.FindByName(const UserName: string): TUser;
begin
raise Exception.Create('FindByName not supported');
end;
function TMyUserManager.FindByPhoneNumber(const PhoneNumber: string): TUser;
begin
raise Exception.Create('FindByPhoneNumber not supported');
end;
function TMyUserManager.GenerateChangeEmailToken(User: TUser; const NewEmail: string): string;
begin
raise Exception.Create('GenerateChangeEmailToken not supported');
end;
function TMyUserManager.GenerateChangePhoneNumberToken(User: TUser; const NewPhoneNumber: string): string;
begin
raise Exception.Create('GenerateChangePhoneNumberToken not supported');
end;
function TMyUserManager.GenerateEmailConfirmationToken(User: TUser): string;
begin
raise Exception.Create('GenerateEmailConfirmationToken not supported');
end;
function TMyUserManager.GeneratePasswordChangeToken(User: TUser): string;
begin
raise Exception.Create('GeneratePasswordChangeToken not supported');
end;
function TMyUserManager.GeneratePasswordResetToken(User: TUser): string;
begin
raise Exception.Create('CeneratePasswordResetToken not supported');
end;
function TMyUserManager.GeneratePhoneNumberConfirmationToken(User: TUser): string;
begin
raise Exception.Create('GeneratePhoneNumberConfirmation not supported');
end;
function TMyUserManager.GenerateUserToken(User: TUser; const TokenProvider, Purpose: string): string;
begin
raise Exception.Create('GenerateUserToken not supported');
end;
function TMyUserManager.GetAuthenticationToken(User: TUser; const LoginProvider, TokenName: string): string;
begin
raise Exception.Create('GetAuthenticationToken not supported');
end;
function TMyUserManager.IsLockedOut(User: TUser): Boolean;
begin
Result := False; // users never get locked out
end;
function TMyUserManager.Manager: TObjectManager;
begin
if FManager = nil then
FManager := TObjectManager.Create(FSphinxManager.Connection);
Result := FManager;
end;
procedure TMyUserManager.RemoveAuthenticationToken(User: TUser; const LoginProvider, TokenName: string);
begin
raise Exception.Create('RemoveAuthenticationToken not supported');
end;
procedure TMyUserManager.ResetAccessFailedCount(User: TUser);
begin
// Do not anything about failed access count
end;
procedure TMyUserManager.SetAuthenticationToken(User: TUser; const LoginProvider, TokenName, TokenValue: string);
begin
raise Exception.Create('SetAuthenticationToken not supported');
end;
procedure TMyUserManager.UpdateUser(User: TUser);
begin
raise Exception.Create('UpdateUser not supported');
end;
procedure TMyUserManager.UsuarioToUser(Source: TUsuarios; Dest: TUser);
begin
Dest.Id := Source.Id.ToString;
Dest.Email := Source.Login;
Dest.UserName := Source.Login;
Dest.EmailConfirmed := True;
Dest.PasswordHash.AsUnicodeString := Source.Pass;
end;
procedure TMyUserManager.ValidatePassword(User: TUser; const Password: string);
begin
raise Exception.Create('ValidatePassword not supported');
end;
procedure TMyUserManager.ValidateUser(User: TUser);
var
Results: TList<IValidationResult>;
Validator: IUserValidator;
ValidatorResult: IValidationResult;
begin
Results := TList<IValidationResult>.Create;
try
// if User.SecurityStamp.ValueOrDefault = '' then
// raise ESphinxException.Create(SEmptySecurityStamp);
for Validator in FUserValidators do
begin
ValidatorResult := Validator.Validate(Self, User);
if not ValidatorResult.Succeeded then
Results.Add(ValidatorResult);
end;
if Results.Count > 0 then
raise EUserValidationException.Create(User.DisplayName, Results);
finally
Results.Free;
end;
end;
function TMyUserManager.VerifyUserToken(User: TUser; const TokenProvider, Purpose, Token: string): Boolean;
begin
raise Exception.Create('VerifyUserToken not supported');
end;
{ TMyUserManagerFactory }
function TMyUserManagerFactory.CreateUserManager(AConfig: ICoreOptions; AManager: TObjectManager;
AOwnsManager: boolean): IUserManager;
begin
Result := TMyUserManager.Create(AConfig, AManager, AOwnsManager);
end;
end.
And register it this way:
type
TInternalConfig = class(TSphinxConfig)
end;
procedure TDataModule2.DataModuleCreate(Sender: TObject);
begin
// Set specific user manager
TInternalConfig(SphinxConfig1).UserManagerFactory := TMyUserManagerFactory.Create;