I'm learning to code with Delphi 12 and I'm trying to create a simple app for an agenda.
I have a mySQL database set up with 2 tables:
users |
---|
id |
username |
password |
appointments |
---|
id |
user_id (FK) |
Company |
Date |
Time |
I already coded a full-stack version of the app but now I'm trying to split the client and server by using TMS XData as framework for my server.
I have declared and mapped the User
and Appointment
entities and the automatically generated CRUD
s work but I need to authorize the operation of reading and writing only the appointment entities bound to the logged user. I created a JWT Login Service
that works and correctly returns the token but I'm not sure on how to modify the CRUD
s to make use of that token to authorize the operations.
How should I proceed?
PS. I'm using FireDAC components to connect to the database.
By reading the DOCs I understood that I should be using both TMS Spark's Middleware
to verify if the token is correct and possibly [EntityAuthorize(EntitySetPermissions*OPERATION*)]
when declaring the object but I'm not sure if this is right and how to check for the right user_id
.
This is what I tried to do but when testing using Postman, it always returns 404. My server is running on "http://localhost:2001/tms/xdata" so what in Postman the URL I'm using is "http://localhost:2001/tmx/xdata/CRUDService/GetAppointments" with method GET and with the Authorization key in the Headers with value "Bearer my-jwt".
unit CRUDServiceImplementation_u;interfaceuses XData.Service.Common, XData.Server.Module, XData.Sys.Exceptions, FireDAC.Comp.Client, Data.DB, AppointmentObj_u, LoginUser_u, GlobalElements_u, System.Generics.Collections, CRUDService_u, JOSE.Core.JWT, JOSE.Core.JWS, JOSE.Core.Builder, Sparkle.Security;type [ServiceImplementation] TCRUDService = class(TInterfacedObject, ICRUDService) private function GetUserID: integer; public [HttpGet] function GetAppointments: TList<TAppointment>; [HttpPost] procedure AddAppointment(const Appointment: TAppointment); [HttpPatch] procedure UpdateAppointment(const Appointment: TAppointment); [HttpDelete] procedure DeleteAppointment(const ID: Integer); end;implementationuses System.SysUtils;function TCRUDService.GetUserID: Integer;var CurrentUserID: Integer; User: IUserIdentity; Query: TFDQuery;begin User := TXDataOperationContext.Current.Request.User; if User = nil then raise EXDataHttpUnauthorized.Create('User not authenticated'); Query := TFDQuery.Create(nil); try Query.Connection := FDConnection1; Query.SQL.Text := 'SELECT Id FROM users WHERE username = :username AND password = :password'; Query.ParamByName('username').AsString := User.Claims['username'].AsString; Query.ParamByName('password').AsString := User.Claims['password'].AsString; Query.Open; if not Query.IsEmpty then CurrentUserID := Query.FieldByName('Id').AsInteger else raise EXDataHttpUnauthorized.Create('User not found or invalid credentials'); finally Query.Free; end; Result := CurrentUserID;end;function TCRUDService.GetAppointments: TList<TAppointment>;var Query: TFDQuery; Appointments: TList<TAppointment>; CurrentUserID: Integer;begin Appointments := TList<TAppointment>.Create; CurrentUserID := GetUserID; // Call the GetUserID function to get the current user's ID Query := TFDQuery.Create(nil); try Query.Connection := FDConnection1; Query.SQL.Text := 'SELECT * FROM appointments WHERE UserID = :UserID'; Query.ParamByName('UserID').AsInteger := CurrentUserID; Query.Open; while not Query.Eof do begin var Appointment := TAppointment.Create; Appointment.ID := Query.FieldByName('ID').AsInteger; Appointment.UserID := Query.FieldByName('UserID').AsInteger; Appointment.Company := Query.FieldByName('Company').AsString; Appointment.Date := Query.FieldByName('Date').AsDateTime; Appointment.Time := Query.FieldByName('Time').AsDateTime; Appointments.Add(Appointment); Query.Next; end; finally Query.Free; end; Result := Appointments;end;procedure TCRUDService.AddAppointment(const Appointment: TAppointment);var Query: TFDQuery; CurrentUserID: Integer;begin CurrentUserID := GetUserID; // Retrieve the current user's ID Query := TFDQuery.Create(nil); try Query.Connection := FDConnection1; Query.SQL.Text := 'INSERT INTO appointments (UserID, Company, Date, Time) VALUES (:UserID, :Company, :Date, :Time)'; Query.ParamByName('UserID').AsInteger := CurrentUserID; Query.ParamByName('Company').AsString := Appointment.Company; Query.ParamByName('Date').AsDateTime := Appointment.Date; Query.ParamByName('Time').AsDateTime := Appointment.Time; Query.ExecSQL; finally Query.Free; end;end;procedure TCRUDService.UpdateAppointment(const Appointment: TAppointment);var Query: TFDQuery; CurrentUserID: Integer;begin CurrentUserID := GetUserID; // Retrieve the current user's ID // Ensure the user owns the appointment Query := TFDQuery.Create(nil); try Query.Connection := FDConnection1; Query.SQL.Text := 'UPDATE appointments SET Company = :Company, Date = :Date, Time = :Time WHERE ID = :ID AND UserID = :UserID'; Query.ParamByName('ID').AsInteger := Appointment.ID; Query.ParamByName('UserID').AsInteger := CurrentUserID; Query.ParamByName('Company').AsString := Appointment.Company; Query.ParamByName('Date').AsDateTime := Appointment.Date; Query.ParamByName('Time').AsDateTime := Appointment.Time; Query.ExecSQL; finally Query.Free; end;end;procedure TCRUDService.DeleteAppointment(const ID: Integer);var Query: TFDQuery; CurrentUserID: Integer;begin CurrentUserID := GetUserID; // Retrieve the current user's ID // Ensure the user owns the appointment Query := TFDQuery.Create(nil); try Query.Connection := FDConnection1; Query.SQL.Text := 'DELETE FROM appointments WHERE ID = :ID AND UserID = :UserID'; Query.ParamByName('ID').AsInteger := ID; Query.ParamByName('UserID').AsInteger := CurrentUserID; Query.ExecSQL; finally Query.Free; end;end;initialization RegisterServiceType(TCRUDService);end.