├── .gitignore ├── README.md └── Server ├── Config.Secrets.pas ├── Config.Server.pas ├── Modules.Api.dfm ├── Modules.Api.pas ├── Modules.Auth.dfm ├── Modules.Auth.pas ├── Modules.Config.dfm ├── Modules.Config.pas ├── Services.Sample.Impl.pas ├── Utils.RSAKeyStorage.pas ├── WinServer.dpr ├── WinServer.dproj ├── config └── local │ ├── WinServer.json │ ├── keys │ ├── 404964ad925b462c891b732813162705.key │ └── 404964ad925b462c891b732813162705.pub │ └── secrets.json └── shared └── Services.Sample.pas /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | 60 | # Delphi history and backups 61 | __history/ 62 | __recovery/ 63 | *.~* 64 | 65 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 66 | *.stat 67 | 68 | # Specific ignores for this repository 69 | Entitlement.TemplateOSX.xml 70 | info.plist.TemplateOSX.xml 71 | tmsbuild._@emb_.tmp 72 | .idea 73 | dunit.ini 74 | *.deployproj 75 | *.dsv 76 | Server/WinServer.res 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TMS BIZ Boilerplate projects 2 | 3 | 4 | -------------------------------------------------------------------------------- /Server/Config.Secrets.pas: -------------------------------------------------------------------------------- 1 | unit Config.Secrets; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, System.SysUtils, System.IOUtils, 7 | Bcl.Json, Bcl.Logging, Bcl.Collections; 8 | 9 | type 10 | TClientConfig = class 11 | strict private 12 | FClientName: string; 13 | FClientId: string; 14 | FClientSecret: string; 15 | FScope: string; 16 | FAudiences: TList; 17 | public 18 | constructor Create; 19 | destructor Destroy; override; 20 | property ClientName: string read FClientName write FClientName; 21 | property ClientId: string read FClientId write FClientId; 22 | property ClientSecret: string read FClientSecret write FClientSecret; 23 | property Scope: string read FScope write FScope; 24 | property Audiences: TList read FAudiences write FAudiences; 25 | end; 26 | 27 | TSecretValues = TOrderedDictionary; 28 | 29 | TSecretsConfig = class 30 | private 31 | FClients: TList; 32 | FEnvVarPrefix: string; 33 | public 34 | constructor Create; 35 | destructor Destroy; override; 36 | function FindClient(const ClientId: string): TClientConfig; 37 | property Clients: TList read FClients; 38 | property EnvVarPrefix: string read FEnvVarPrefix; 39 | end; 40 | 41 | procedure SetSecretsConfigFileName(const FileName: string); 42 | function SecretsConfig: TSecretsConfig; 43 | 44 | implementation 45 | 46 | var 47 | _SecretsConfigFileName: string; 48 | _SecretsConfig: TSecretsConfig; 49 | 50 | procedure SetSecretsConfigFileName(const FileName: string); 51 | begin 52 | _SecretsConfigFileName := FileName; 53 | FreeAndNil(_SecretsConfig); 54 | end; 55 | 56 | function SecretsConfig: TSecretsConfig; 57 | begin 58 | if _SecretsConfig = nil then 59 | begin 60 | var ConfigFile := _SecretsConfigFileName; 61 | if TFile.Exists(ConfigFile) then 62 | try 63 | _SecretsConfig := TJson.Deserialize(TFile.ReadAllText(ConfigFile)); 64 | except 65 | on E: Exception do 66 | LogManager.GetLogger.Error(Format('Error reading secrets config file %s: %s (%s)', [TPath.GetFileName(ConfigFile), E.Message, E.ClassName])); 67 | end 68 | else 69 | _SecretsConfig := TSecretsConfig.Create; 70 | end; 71 | Result := _SecretsConfig; 72 | end; 73 | 74 | { TClientConfig } 75 | 76 | constructor TClientConfig.Create; 77 | begin 78 | FAudiences := TList.Create; 79 | end; 80 | 81 | destructor TClientConfig.Destroy; 82 | begin 83 | FAudiences.Free; 84 | inherited; 85 | end; 86 | 87 | { TSecretsConfig } 88 | 89 | constructor TSecretsConfig.Create; 90 | begin 91 | FClients := TObjectList.Create; 92 | end; 93 | 94 | destructor TSecretsConfig.Destroy; 95 | begin 96 | FClients.Free; 97 | inherited; 98 | end; 99 | 100 | function TSecretsConfig.FindClient(const ClientId: string): TClientConfig; 101 | begin 102 | for var Client in Clients do 103 | if Client.ClientId = ClientId then 104 | Exit(Client); 105 | Result := nil; 106 | end; 107 | 108 | initialization 109 | _SecretsConfig := nil; 110 | _SecretsConfigFileName := ''; 111 | 112 | finalization 113 | _SecretsConfig.Free; 114 | 115 | end. 116 | -------------------------------------------------------------------------------- /Server/Config.Server.pas: -------------------------------------------------------------------------------- 1 | unit Config.Server; 2 | 3 | interface 4 | 5 | uses 6 | Generics.Collections, SysUtils, IOUtils, Bcl.Json.Attributes, Bcl.Json.NamingStrategies, Bcl.Json, Bcl.Logging, 7 | Config.Secrets; 8 | 9 | type 10 | [JsonManaged] 11 | [JsonInclude(TInclusionMode.NonDefault)] 12 | TJwtMiddlewareConfig = class 13 | strict private 14 | FExpectedIssuers: TList; 15 | FExpectedAudiences: TList; 16 | public 17 | constructor Create; 18 | destructor Destroy; override; 19 | property ExpectedIssuers: TList read FExpectedIssuers; 20 | property ExpectedAudiences: TList read FExpectedAudiences; 21 | end; 22 | 23 | TMiddlewareListConfig = class 24 | strict private 25 | FJwt: TJwtMiddlewareConfig; 26 | public 27 | constructor Create; 28 | destructor Destroy; override; 29 | property Jwt: TJwtMiddlewareConfig read FJwt write FJwt; 30 | end; 31 | 32 | [JsonManaged] 33 | [JsonInclude(TInclusionMode.NonDefault)] 34 | TModuleConfig = class 35 | strict private 36 | FBaseUrl: string; 37 | FMiddleware: TMiddlewareListConfig; 38 | FSigningKeyId: string; 39 | public 40 | constructor Create; 41 | destructor Destroy; override; 42 | property BaseUrl: string read FBaseUrl write FBaseUrl; 43 | property SigningKeyId: string read FSigningKeyId write FSigningKeyId; 44 | property Middleware: TMiddlewareListConfig read FMiddleware; 45 | end; 46 | 47 | [JsonManaged] 48 | [JsonInclude(TInclusionMode.NonDefault)] 49 | TWinServiceConfig = class 50 | strict private 51 | FName: string; 52 | FDisplayName: string; 53 | FDescription: string; 54 | public 55 | property Name: string read FName; 56 | property DisplayName: string read FDisplayName; 57 | property Description: string read FDescription; 58 | end; 59 | 60 | [JsonManaged] 61 | [JsonInclude(TInclusionMode.NonDefault)] 62 | TServerConfig = class 63 | private 64 | FApiModule: TModuleConfig; 65 | FAuthModule: TModuleConfig; 66 | FRSAKeysFolder: string; 67 | FSecretsFile: string; 68 | FWinService: TWinServiceConfig; 69 | function FullFolder(const Value: string): string; 70 | public 71 | constructor Create; 72 | destructor Destroy; override; 73 | function FullRSAKeysFolder: string; 74 | function FullSecretsFile: string; 75 | 76 | property SecretsFile: string write FSecretsFile; 77 | property ApiModule: TModuleConfig read FApiModule; 78 | property AuthModule: TModuleConfig read FAuthModule; 79 | property WinService: TWinServiceConfig read FWinService; 80 | end; 81 | 82 | function ServerConfig: TServerConfig; 83 | procedure ReloadServerConfig(const FileName: string); 84 | procedure CheckServerConfig; 85 | 86 | implementation 87 | 88 | var 89 | _ServerConfig: TServerConfig; 90 | 91 | procedure CheckNotEmpty(const Name, Value: string); 92 | begin 93 | if Value = '' then 94 | raise Exception.CreateFmt('Configuration parameter "%s" must not be empty', [Name]); 95 | end; 96 | 97 | procedure CheckServerConfig; 98 | begin 99 | if not TFile.Exists(ServerConfig.FullSecretsFile) then 100 | raise Exception.Create('Invalid secrets file: ' + ServerConfig.FullSecretsFile); 101 | CheckNotEmpty('ApiModule.BaseUrl', ServerConfig.ApiModule.BaseUrl); 102 | CheckNotEmpty('AuthModule.BaseUrl', ServerConfig.AuthModule.BaseUrl); 103 | end; 104 | 105 | function ServerConfigFileName(Suffix: string = ''): string; 106 | var 107 | JsonFileName: string; 108 | Dir: string; 109 | begin 110 | JsonFileName := TPath.GetFileName(TPath.ChangeExtension(ParamStr(0), Format('%s.json', [Suffix]))); 111 | Dir := TPath.GetDirectoryName(ParamStr(0)); 112 | 113 | Result := TPath.Combine(Dir, JsonFileName); 114 | if TFile.Exists(Result) then 115 | Exit; 116 | 117 | Result := TPath.GetFullPath(TPath.Combine(Dir, '..\..\config\local')); 118 | Result := TPath.Combine(Result, JsonFileName); 119 | if TFile.Exists(Result) then 120 | Exit; 121 | 122 | raise Exception.CreateFmt('Config file %s not found', [JsonFileName]); 123 | end; 124 | 125 | procedure ReloadServerConfig(const FileName: string); 126 | begin 127 | FreeAndNil(_ServerConfig); 128 | if TFile.Exists(FileName) then 129 | try 130 | _ServerConfig := TJson.Deserialize(TFile.ReadAllText(FileName)); 131 | except 132 | on E: Exception do 133 | LogManager.GetLogger.Error(Format('Error reading config file %s: %s (%s)', [TPath.GetFileName(FileName), E.Message, E.ClassName])); 134 | end 135 | else 136 | _ServerConfig := TServerConfig.Create; 137 | SetSecretsConfigFileName(_ServerConfig.FullSecretsFile); 138 | end; 139 | 140 | function ServerConfig: TServerConfig; 141 | begin 142 | if _ServerConfig = nil then 143 | ReloadServerConfig(ServerConfigFileName); 144 | Result := _ServerConfig; 145 | end; 146 | 147 | { TServerConfig } 148 | 149 | constructor TServerConfig.Create; 150 | begin 151 | inherited Create; 152 | FApiModule := TModuleConfig.Create; 153 | FAuthModule := TModuleConfig.Create; 154 | FWinService := TWinServiceConfig.Create; 155 | end; 156 | 157 | destructor TServerConfig.Destroy; 158 | begin 159 | FApiModule.Free; 160 | FAuthModule.Free; 161 | FWinService.Free; 162 | inherited; 163 | end; 164 | 165 | function TServerConfig.FullFolder(const Value: string): string; 166 | begin 167 | if TPath.IsRelativePath(Value) then 168 | Result := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), Value) 169 | else 170 | Result := Value; 171 | end; 172 | 173 | function TServerConfig.FullRSAKeysFolder: string; 174 | begin 175 | Result := FullFolder(FRSAKeysFolder); 176 | end; 177 | 178 | function TServerConfig.FullSecretsFile: string; 179 | begin 180 | Result := FullFolder(FSecretsFile); 181 | end; 182 | 183 | { TJwtMiddlewareConfig } 184 | 185 | constructor TJwtMiddlewareConfig.Create; 186 | begin 187 | FExpectedIssuers := TList.Create; 188 | FExpectedAudiences := TList.Create; 189 | end; 190 | 191 | destructor TJwtMiddlewareConfig.Destroy; 192 | begin 193 | FExpectedIssuers.Free; 194 | FExpectedAudiences.Free; 195 | inherited; 196 | end; 197 | 198 | { TModuleConfig } 199 | 200 | constructor TModuleConfig.Create; 201 | begin 202 | FMiddleware := TMiddlewareListConfig.Create; 203 | end; 204 | 205 | destructor TModuleConfig.Destroy; 206 | begin 207 | FMiddleware.Free; 208 | inherited; 209 | end; 210 | 211 | { TMiddlewareListConfig } 212 | 213 | constructor TMiddlewareListConfig.Create; 214 | begin 215 | FJwt := TJwtMiddlewareConfig.Create; 216 | end; 217 | 218 | destructor TMiddlewareListConfig.Destroy; 219 | begin 220 | FJwt.Free; 221 | inherited; 222 | end; 223 | 224 | initialization 225 | _ServerConfig := nil; 226 | 227 | finalization 228 | _ServerConfig.Free; 229 | 230 | end. 231 | -------------------------------------------------------------------------------- /Server/Modules.Api.dfm: -------------------------------------------------------------------------------- 1 | object MainModule: TMainModule 2 | OnCreate = DataModuleCreate 3 | Height = 394 4 | Width = 602 5 | object ApiServer: TXDataServer 6 | UnknownMemberHandling = Error 7 | InstanceLoopHandling = Error 8 | EntitySetPermissions = <> 9 | SwaggerOptions.Enabled = True 10 | SwaggerUIOptions.Enabled = True 11 | Left = 56 12 | Top = 56 13 | object ApiServerGeneric: TSparkleGenericMiddleware 14 | OnRequest = ApiServerGenericRequest 15 | end 16 | object ApiServerForward: TSparkleForwardMiddleware 17 | end 18 | object ApiServerLogging: TSparkleLoggingMiddleware 19 | FormatString = ':method :url :statuscode - :responsetime ms' 20 | ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s' 21 | ErrorResponseOptions.ErrorCode = 'ServerError' 22 | ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s' 23 | end 24 | object ApiServerCompress: TSparkleCompressMiddleware 25 | end 26 | object ApiServerJWT: TSparkleJwtMiddleware 27 | ForbidAnonymousAccess = True 28 | OnGetSecretEx = JWTGetSecretEx 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /Server/Modules.Api.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Api; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, System.SysUtils, System.Classes, Sparkle.HttpServer.Module, Sparkle.HttpServer.Context, 7 | Sparkle.Comp.Server, XData.Comp.Server, Sparkle.Comp.GenericMiddleware, Sparkle.Comp.LoggingMiddleware, 8 | XData.Server.Module, Config.Server, Sparkle.Comp.JwtMiddleware, 9 | Sparkle.Comp.ForwardMiddleware, Sparkle.Comp.CompressMiddleware; 10 | 11 | type 12 | TMainModule = class(TDataModule) 13 | ApiServer: TXDataServer; 14 | ApiServerLogging: TSparkleLoggingMiddleware; 15 | ApiServerJWT: TSparkleJwtMiddleware; 16 | ApiServerForward: TSparkleForwardMiddleware; 17 | ApiServerCompress: TSparkleCompressMiddleware; 18 | ApiServerGeneric: TSparkleGenericMiddleware; 19 | procedure DataModuleCreate(Sender: TObject); 20 | procedure JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext; 21 | var Secret: TBytes); 22 | procedure ApiServerGenericRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc); 23 | private 24 | procedure ConfigureJwtMiddleware(Config: TJwtMiddlewareConfig; Middleware: TSparkleJwtMiddleware); 25 | public 26 | end; 27 | 28 | var 29 | MainModule: TMainModule; 30 | 31 | implementation 32 | 33 | {%CLASSGROUP 'System.Classes.TPersistent'} 34 | 35 | uses 36 | System.IOUtils, Bcl.Jose.Consumer, Bcl.Utils, Bcl.Logging, Bcl.Json.BaseObjectConverter, 37 | Utils.RSAKeyStorage; 38 | 39 | {$R *.dfm} 40 | 41 | procedure TMainModule.DataModuleCreate(Sender: TObject); 42 | begin 43 | // Set servers properties based on config file 44 | ApiServer.BaseUrl := ServerConfig.ApiModule.BaseUrl; 45 | ConfigureJWTMiddleware(ServerConfig.ApiModule.Middleware.Jwt, ApiServerJWT); 46 | 47 | // Check cofig 48 | CheckServerConfig; 49 | 50 | // Log start 51 | var Logger := LogManager.GetLogger; 52 | Logger.Info('Api data module created'); 53 | end; 54 | 55 | procedure TMainModule.JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext; 56 | var Secret: TBytes); 57 | begin 58 | if JWT.Header.Algorithm = 'RS256' then 59 | Secret := RSAKeyStorage.PublicKey(JWT.Header.KeyID) 60 | else 61 | raise EInvalidJWTException.CreateFmt( 62 | 'JWS algorithm [%s] is not supported', [JWT.Header.Algorithm]); 63 | end; 64 | 65 | procedure TMainModule.ApiServerGenericRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc); 66 | begin 67 | Context.Response.OnHeaders( 68 | procedure(Response: THttpServerResponse) 69 | begin 70 | Response.Headers.SetValue('Server-Version', '1'); 71 | end); 72 | Next(Context); 73 | end; 74 | 75 | procedure TMainModule.ConfigureJwtMiddleware(Config: TJwtMiddlewareConfig; Middleware: TSparkleJwtMiddleware); 76 | begin 77 | Middleware.RequireExpirationTime := True; 78 | Middleware.AllowExpiredToken := False; 79 | Middleware.ForbidAnonymousAccess := False; 80 | 81 | for var Issuer in Config.ExpectedIssuers do 82 | Middleware.ExpectedIssuers.Add(Issuer); 83 | if Middleware.ExpectedIssuers.Count = 0 then 84 | Middleware.ExpectedIssuers.Add('Unknown'); 85 | 86 | for var Audience in Config.ExpectedAudiences do 87 | Middleware.ExpectedAudiences.Add(Audience); 88 | if Middleware.ExpectedAudiences.Count = 0 then 89 | Middleware.ExpectedAudiences.Add('Unknown'); 90 | end; 91 | 92 | end. 93 | -------------------------------------------------------------------------------- /Server/Modules.Auth.dfm: -------------------------------------------------------------------------------- 1 | object AuthModule: TAuthModule 2 | OnCreate = DataModuleCreate 3 | Height = 364 4 | Width = 516 5 | object SphinxConfig: TSphinxConfig 6 | Clients = <> 7 | OnGetClient = SphinxConfigGetClient 8 | OnConfigureToken = SphinxConfigConfigureToken 9 | OnGetSigningData = SphinxConfigGetSigningData 10 | Left = 80 11 | Top = 48 12 | end 13 | object SphinxServer: TSphinxServer 14 | Config = SphinxConfig 15 | Left = 80 16 | Top = 128 17 | object SphinxServerForward: TSparkleForwardMiddleware 18 | end 19 | object SphinxServerLogging: TSparkleLoggingMiddleware 20 | FormatString = ':method :url :statuscode - :responsetime ms' 21 | ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s' 22 | ErrorResponseOptions.ErrorCode = 'ServerError' 23 | ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s' 24 | end 25 | object SphinxServerCompress: TSparkleCompressMiddleware 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /Server/Modules.Auth.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Auth; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, Sparkle.HttpServer.Module, Sparkle.HttpServer.Context, Sphinx.Server.Module, 7 | Sparkle.Comp.Server, XData.Comp.Server, Sphinx.Comp.Server, Sphinx.Comp.Config, 8 | System.Hash, Sparkle.Comp.CompressMiddleware, Sparkle.Comp.LoggingMiddleware, Sparkle.Comp.ForwardMiddleware, 9 | Config.Secrets; 10 | 11 | type 12 | TAuthModule = class(TDataModule) 13 | SphinxConfig: TSphinxConfig; 14 | SphinxServer: TSphinxServer; 15 | SphinxServerForward: TSparkleForwardMiddleware; 16 | SphinxServerLogging: TSparkleLoggingMiddleware; 17 | SphinxServerCompress: TSparkleCompressMiddleware; 18 | procedure SphinxConfigGetClient(Sender: TObject; Client: TSphinxClientApp; var Accept: Boolean); 19 | procedure SphinxConfigGetSigningData(Sender: TObject; Args: TGetSigningDataArgs); 20 | procedure DataModuleCreate(Sender: TObject); 21 | procedure SphinxConfigConfigureToken(Sender: TObject; Args: TConfigureTokenArgs); 22 | public 23 | end; 24 | 25 | var 26 | AuthModule: TAuthModule; 27 | 28 | implementation 29 | 30 | {%CLASSGROUP 'System.Classes.TPersistent'} 31 | 32 | uses 33 | System.TimeSpan, System.StrUtils, Config.Server, Utils.RSAKeyStorage, Bcl.Logging, Bcl.Json.Classes, Sphinx.Consts; 34 | 35 | {%CLASSGROUP 'System.Classes.TPersistent'} 36 | 37 | {$R *.dfm} 38 | 39 | procedure TAuthModule.DataModuleCreate(Sender: TObject); 40 | begin 41 | SphinxServer.BaseUrl := ServerConfig.AuthModule.BaseUrl; 42 | 43 | LogManager.GetLogger.Info(Format('Secrets file: total of %d client secrets configured', [SecretsConfig.Clients.Count])); 44 | 45 | RSAKeyStorage := TRSAKeyStorage.Create(ServerConfig.FullRSAKeysFolder); 46 | end; 47 | 48 | procedure TAuthModule.SphinxConfigConfigureToken(Sender: TObject; Args: TConfigureTokenArgs); 49 | begin 50 | // Add client audience 51 | var Audiences := SplitString(Args.Client.GetParam(JwtClaimNames.Audience), ','); 52 | if Length(Audiences) = 1 then 53 | Args.Token.Claims.AddOrSet(JwtClaimNames.Audience, Audiences[0]) 54 | else 55 | if Length(Audiences) > 1 then 56 | 57 | begin 58 | var AudienceClaim := Sparkle.Security.TUserClaim.Create(JwtClaimNames.Audience); 59 | Args.Token.Claims.AddOrSet(AudienceClaim); 60 | var JAudiences := TJArray.Create; 61 | AudienceClaim.SetJElement(JAudiences); 62 | for var Aud in Audiences do 63 | JAudiences.Add(Aud); 64 | end; 65 | 66 | // add email and code if present 67 | var Email := Args.Client.GetParam('email'); 68 | var Code := Args.Client.GetParam('code'); 69 | if (Email <> '') and (Code <> '') then 70 | begin 71 | Args.Token.Claims.AddOrSet('email', Email); 72 | // Args.Token.Claims.AddOrSet('code', Code); 73 | 74 | // Remove the client_id claim 75 | Args.Token.Claims.Remove(JwtClaimNames.ClientId); 76 | end; 77 | 78 | end; 79 | 80 | procedure TAuthModule.SphinxConfigGetClient(Sender: TObject; Client: TSphinxClientApp; var Accept: Boolean); 81 | begin 82 | // Reject by default 83 | Accept := False; 84 | 85 | // First check if the client is a valid admin client 86 | begin 87 | var Config := SecretsConfig.FindClient(Client.ClientId); 88 | if Config <> nil then 89 | begin 90 | Accept := True; 91 | Client.RequireClientSecret := True; 92 | Client.AddSha256Secret(THashSHA2.GetHashBytes(Config.ClientSecret)); 93 | Client.AccessTokenLifetimeSpan := TTimeSpan.FromHours(1); 94 | Client.AllowedGrantTypes := [TGrantType.gtClientCredentials]; 95 | Client.ValidScopes.AddStrings(SplitString(Config.Scope, ' ')); 96 | Client.DefaultScopeValues.Assign(Client.ValidScopes); 97 | Client.AddCustomParam(JwtClaimNames.Audience, string.Join(',', Config.Audiences.ToArray)); 98 | Exit; 99 | end 100 | end; 101 | end; 102 | 103 | procedure TAuthModule.SphinxConfigGetSigningData(Sender: TObject; Args: TGetSigningDataArgs); 104 | begin 105 | Args.Data.Algorithm := 'RS256'; 106 | Args.Data.KeyId := ServerConfig.AuthModule.SigningKeyId; 107 | Args.Data.Key := RSAKeyStorage.PrivateKey(Args.Data.KeyId); 108 | end; 109 | 110 | end. 111 | -------------------------------------------------------------------------------- /Server/Modules.Config.dfm: -------------------------------------------------------------------------------- 1 | object ConfigModule: TConfigModule 2 | OnCreate = DataModuleCreate 3 | Height = 480 4 | Width = 640 5 | end 6 | -------------------------------------------------------------------------------- /Server/Modules.Config.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Config; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, 7 | Bcl.Logging, Bcl.TMSLogging, TMSLoggingCore, TMSLoggingUtils, Sparkle.App.Config, 8 | TMSLoggingTextOutputHandler, 9 | TMSLoggingDiscordOutputHandler; 10 | 11 | type 12 | TConfigModule = class(TDataModule) 13 | procedure DataModuleCreate(Sender: TObject); 14 | private 15 | { Private declarations } 16 | public 17 | { Public declarations } 18 | end; 19 | 20 | var 21 | ConfigModule: TConfigModule; 22 | 23 | implementation 24 | 25 | uses 26 | Config.Server; 27 | 28 | {%CLASSGROUP 'Vcl.Controls.TControl'} 29 | 30 | {$R *.dfm} 31 | 32 | procedure TConfigModule.DataModuleCreate(Sender: TObject); 33 | begin 34 | // Output log to file 35 | RegisterTMSLogger; 36 | begin 37 | var Handler := TMSDefaultLogger.RegisterOutputHandlerClass(TTMSLoggerTextOutputHandler); 38 | Handler.LogLevelFilters := [Warning, Error, Exception, Info]; 39 | TTMSLoggerTextOutputHandler(Handler).FileName := TPath.ChangeExtension(ParamStr(0), '.log'); 40 | end; 41 | 42 | // logging file config 43 | var LoggingFile := TPath.ChangeExtension(ParamStr(0), '.logging.ini'); 44 | if TFile.Exists(LoggingFile) then 45 | TMSDefaultLogger.LoadConfigurationFromFile(nil, LoggingFile); 46 | 47 | // Service naming 48 | SparkleAppConfig.WinService.Name := ServerConfig.WinService.Name; 49 | SparkleAppConfig.WinService.DisplayName := ServerConfig.WinService.DisplayName; 50 | SparkleAppConfig.WinService.Description := ServerConfig.WinService.Description; 51 | end; 52 | 53 | end. 54 | -------------------------------------------------------------------------------- /Server/Services.Sample.Impl.pas: -------------------------------------------------------------------------------- 1 | unit Services.Sample.Impl; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | XData.Service.Common, 8 | Services.Sample; 9 | 10 | type 11 | [ServiceImplementation] 12 | TSampleService = class(TInterfacedObject, ISampleService) 13 | public 14 | { ISampleService methods } 15 | function EchoString(const Value: string): string; 16 | function Add(const A, B: Double): Double; 17 | end; 18 | 19 | implementation 20 | 21 | { TSampleService } 22 | 23 | function TSampleService.Add(const A, B: Double): Double; 24 | begin 25 | Result := A + B; 26 | end; 27 | 28 | function TSampleService.EchoString(const Value: string): string; 29 | begin 30 | Result := Value; 31 | end; 32 | 33 | initialization 34 | RegisterServiceType(TSampleService); 35 | 36 | end. 37 | 38 | -------------------------------------------------------------------------------- /Server/Utils.RSAKeyStorage.pas: -------------------------------------------------------------------------------- 1 | unit Utils.RSAKeyStorage; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.Generics.Collections, 7 | System.SyncObjs; 8 | 9 | type 10 | IRSAKeyStorage = interface 11 | ['{747F49FD-A072-48BC-8DA1-DBB55DF17F4A}'] 12 | function PrivateKey(const KeyId: string): TBytes; 13 | function PublicKey(const KeyId: string): TBytes; 14 | end; 15 | 16 | TKeyPair = record 17 | PrivateKey: TBytes; 18 | PublicKey: TBytes; 19 | end; 20 | 21 | TRSAKeyStorage = class(TInterfacedObject, IRSAKeyStorage) 22 | strict private 23 | FKeysFolder: string; 24 | FPrivateKeyFileExtension: string; 25 | FPublicKeyFileExtension: string; 26 | FKeys: TDictionary; 27 | FCriticalSection: TCriticalSection; 28 | function LoadKeyPair(const KeyId: string): TKeyPair; 29 | public 30 | constructor Create(const AKeysFolder: string; 31 | const APrivateKeyFileExtension: string = '.key'; 32 | const APublicKeyFileExtension: string = '.pub'); 33 | destructor Destroy; override; 34 | function PrivateKey(const KeyId: string): TBytes; 35 | function PublicKey(const KeyId: string): TBytes; 36 | end; 37 | 38 | ERSAKeyStorageException = class(Exception); 39 | 40 | ERSAKeyNotFound = class(ERSAKeyStorageException); 41 | 42 | var 43 | RSAKeyStorage: IRSAKeyStorage; 44 | 45 | implementation 46 | 47 | uses 48 | System.IOUtils; 49 | 50 | { TRSAKeyStorage } 51 | 52 | constructor TRSAKeyStorage.Create(const AKeysFolder, APrivateKeyFileExtension, APublicKeyFileExtension: string); 53 | begin 54 | FKeysFolder := AKeysFolder; 55 | FPrivateKeyFileExtension := APrivateKeyFileExtension; 56 | FPublicKeyFileExtension := APublicKeyFileExtension; 57 | FKeys := TDictionary.Create; 58 | FCriticalSection := TCriticalSection.Create; 59 | end; 60 | 61 | destructor TRSAKeyStorage.Destroy; 62 | begin 63 | FCriticalSection.Free; 64 | FKeys.Free; 65 | inherited; 66 | end; 67 | 68 | function TRSAKeyStorage.LoadKeyPair(const KeyId: string): TKeyPair; 69 | 70 | function GetKeyFileName(const KeyFileExtension: string): string; 71 | begin 72 | Result := TPath.ChangeExtension(TPath.Combine(FKeysFolder, KeyId), KeyFileExtension); 73 | end; 74 | 75 | function GetKey(const KeyFileName: string): TBytes; 76 | begin 77 | if TFile.Exists(KeyFileName) then 78 | Result := TFile.ReadAllBytes(KeyFileName) 79 | else 80 | Result := []; 81 | end; 82 | 83 | begin 84 | FCriticalSection.Acquire; 85 | try 86 | if FKeys.ContainsKey(KeyId) then 87 | Exit(FKeys[KeyId]); 88 | Result.PrivateKey := GetKey(GetKeyFileName(FPrivateKeyFileExtension)); 89 | Result.PublicKey := GetKey(GetKeyFileName(FPublicKeyFileExtension)); 90 | FKeys.AddOrSetValue(KeyId, Result); 91 | finally 92 | FCriticalSection.Release; 93 | end; 94 | end; 95 | 96 | function TRSAKeyStorage.PrivateKey(const KeyId: string): TBytes; 97 | var 98 | KeyPair: TKeyPair; 99 | begin 100 | KeyPair := LoadKeyPair(KeyId); 101 | Result := KeyPair.PrivateKey; 102 | end; 103 | 104 | function TRSAKeyStorage.PublicKey(const KeyId: string): TBytes; 105 | var 106 | KeyPair: TKeyPair; 107 | begin 108 | KeyPair := LoadKeyPair(KeyId); 109 | Result := KeyPair.PublicKey; 110 | end; 111 | 112 | initialization 113 | 114 | finalization 115 | RSAKeyStorage := nil; 116 | 117 | end. 118 | -------------------------------------------------------------------------------- /Server/WinServer.dpr: -------------------------------------------------------------------------------- 1 | program WinServer; 2 | 3 | uses 4 | Sparkle.App, 5 | Vcl.Forms, 6 | Modules.Api in 'Modules.Api.pas' {MainModule: TDataModule}, 7 | Services.Sample in 'shared\Services.Sample.pas', 8 | Modules.Config in 'Modules.Config.pas' {ConfigModule: TDataModule}, 9 | Config.Server in 'Config.Server.pas', 10 | Services.Sample.Impl in 'Services.Sample.Impl.pas', 11 | Modules.Auth in 'Modules.Auth.pas' {AuthModule: TDataModule}, 12 | Config.Secrets in 'Config.Secrets.pas', 13 | Utils.RSAKeyStorage in 'Utils.RSAKeyStorage.pas'; 14 | 15 | {$R *.res} 16 | 17 | begin 18 | ReportMemoryLeaksOnShutdown := True; 19 | Application.Initialize; 20 | Application.MainFormOnTaskbar := True; 21 | Application.CreateForm(TConfigModule, ConfigModule); 22 | Application.CreateForm(TMainModule, MainModule); 23 | Application.CreateForm(TAuthModule, AuthModule); 24 | Application.Run; 25 | end. 26 | -------------------------------------------------------------------------------- /Server/WinServer.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {464BCDC4-FAF3-4913-83C2-EA352AFE2ECF} 4 | 20.2 5 | VCL 6 | True 7 | Debug 8 | Win32 9 | 1 10 | Application 11 | WinServer.dpr 12 | WinServer 13 | 14 | 15 | true 16 | 17 | 18 | true 19 | Base 20 | true 21 | 22 | 23 | true 24 | Base 25 | true 26 | 27 | 28 | true 29 | Base 30 | true 31 | 32 | 33 | true 34 | Cfg_1 35 | true 36 | true 37 | 38 | 39 | true 40 | Base 41 | true 42 | 43 | 44 | true 45 | Cfg_2 46 | true 47 | true 48 | 49 | 50 | .\$(Platform)\$(Config) 51 | .\$(Platform)\$(Config) 52 | false 53 | false 54 | false 55 | false 56 | false 57 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 58 | $(BDS)\bin\delphi_PROJECTICON.ico 59 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 60 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 61 | WinServer 62 | 63 | 64 | TMSScripter_Memo;vclwinx;DataSnapServer;ACBr_TCP;dacvcl280;ACBr_MDFeDamdfeFR;ibdac280;FixInsight_11;TMSLogging;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;aurelius;appanalytics;IndyProtocols;vclx;ACBr_OpenSSL;sparkle;IndyIPClient;dbxcds;vcledge;sphinx;dac280;bindcompvclwinx;ibdacfmx280;FmxTeeUI;AureliusIBDAC;ibdacvcl280;emsedge;bindcompfmx;DBXFirebirdDriver;ACBr_CTeDacteFR;GraphQLHttp;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;TMSScripter_IDE;Tee;soapmidas;JclVcl;vclactnband;TeeUI;TMSScripter;fmxFireDAC;dbexpress;Jcl;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;dacfmx280;vcltouch;fmxase;TMSWEBCorePkgLibDXE14;DBXOdbcDriver;dbrtl;TMSDiagram;FireDACDBXDriver;FireDACOracleDriver;TMSWEBCorePkgDXE14;fmxdae;TeeDB;GraphQLCore;FireDACMSAccDriver;tmsbcl;CustomIPTransport;FireDACMSSQLDriver;ACBr_NFSeX;DataSnapIndy10ServerTransport;JclDeveloperTools;ACBr_NFe;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;ACBr_Diversos;FireDACTDataDriver;vcldb;ACBr_Integrador;ACBr_PIXCD;JclContainers;vclFireDAC;ACBr_CTe;ACBr_BoletoFR;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;TMSScripter_VCL;bindcompdbx;rtl;FireDACMySQLDriver;ACBr_NFeDanfeFR;FireDACADSDriver;remotedb;RESTComponents;ACBr_NFSeXDANFSeFR;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;TMSScripter_Legacy;DBXSybaseASEDriver;ACBr_Serial;DBXDb2Driver;ACBr_Comum;ACBr_MDFe;ACBr_NFeDanfeESCPOS;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ACBr_Boleto;crcontrols280;bindcompvcl;dsnap;xdata;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 65 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 66 | Debug 67 | true 68 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 69 | 1033 70 | $(BDS)\bin\default_app.manifest 71 | 72 | 73 | vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;GraphQLHttp;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;GraphQLCore;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;vcldb;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 74 | 75 | 76 | DEBUG;$(DCC_Define) 77 | true 78 | false 79 | true 80 | true 81 | true 82 | true 83 | true 84 | 85 | 86 | false 87 | PerMonitorV2 88 | 89 | 90 | false 91 | RELEASE;$(DCC_Define) 92 | 0 93 | 0 94 | 95 | 96 | PerMonitorV2 97 | 98 | 99 | 100 | MainSource 101 | 102 | 103 |
MainModule
104 | TDataModule 105 |
106 | 107 | 108 |
ConfigModule
109 | TDataModule 110 |
111 | 112 | 113 | 114 |
AuthModule
115 | TDataModule 116 |
117 | 118 | 119 | 120 | Base 121 | 122 | 123 | Cfg_1 124 | Base 125 | 126 | 127 | Cfg_2 128 | Base 129 | 130 |
131 | 132 | Delphi.Personality.12 133 | Application 134 | 135 | 136 | 137 | WinServer.dpr 138 | 139 | 140 | 141 | 142 | 143 | WinServer.exe 144 | true 145 | 146 | 147 | 148 | 149 | 150 | 151 | 1 152 | 153 | 154 | Contents\MacOS 155 | 1 156 | 157 | 158 | 0 159 | 160 | 161 | 162 | 163 | res\xml 164 | 1 165 | 166 | 167 | res\xml 168 | 1 169 | 170 | 171 | 172 | 173 | library\lib\armeabi 174 | 1 175 | 176 | 177 | library\lib\armeabi 178 | 1 179 | 180 | 181 | 182 | 183 | library\lib\armeabi-v7a 184 | 1 185 | 186 | 187 | 188 | 189 | library\lib\mips 190 | 1 191 | 192 | 193 | library\lib\mips 194 | 1 195 | 196 | 197 | 198 | 199 | library\lib\armeabi-v7a 200 | 1 201 | 202 | 203 | library\lib\arm64-v8a 204 | 1 205 | 206 | 207 | 208 | 209 | library\lib\armeabi-v7a 210 | 1 211 | 212 | 213 | 214 | 215 | res\drawable 216 | 1 217 | 218 | 219 | res\drawable 220 | 1 221 | 222 | 223 | 224 | 225 | res\drawable-anydpi-v21 226 | 1 227 | 228 | 229 | res\drawable-anydpi-v21 230 | 1 231 | 232 | 233 | 234 | 235 | res\values 236 | 1 237 | 238 | 239 | res\values 240 | 1 241 | 242 | 243 | 244 | 245 | res\values-v21 246 | 1 247 | 248 | 249 | res\values-v21 250 | 1 251 | 252 | 253 | 254 | 255 | res\values-v31 256 | 1 257 | 258 | 259 | res\values-v31 260 | 1 261 | 262 | 263 | 264 | 265 | res\drawable-anydpi-v26 266 | 1 267 | 268 | 269 | res\drawable-anydpi-v26 270 | 1 271 | 272 | 273 | 274 | 275 | res\drawable 276 | 1 277 | 278 | 279 | res\drawable 280 | 1 281 | 282 | 283 | 284 | 285 | res\drawable 286 | 1 287 | 288 | 289 | res\drawable 290 | 1 291 | 292 | 293 | 294 | 295 | res\drawable 296 | 1 297 | 298 | 299 | res\drawable 300 | 1 301 | 302 | 303 | 304 | 305 | res\drawable-anydpi-v33 306 | 1 307 | 308 | 309 | res\drawable-anydpi-v33 310 | 1 311 | 312 | 313 | 314 | 315 | res\values 316 | 1 317 | 318 | 319 | res\values 320 | 1 321 | 322 | 323 | 324 | 325 | res\values-night-v21 326 | 1 327 | 328 | 329 | res\values-night-v21 330 | 1 331 | 332 | 333 | 334 | 335 | res\drawable 336 | 1 337 | 338 | 339 | res\drawable 340 | 1 341 | 342 | 343 | 344 | 345 | res\drawable-xxhdpi 346 | 1 347 | 348 | 349 | res\drawable-xxhdpi 350 | 1 351 | 352 | 353 | 354 | 355 | res\drawable-xxxhdpi 356 | 1 357 | 358 | 359 | res\drawable-xxxhdpi 360 | 1 361 | 362 | 363 | 364 | 365 | res\drawable-ldpi 366 | 1 367 | 368 | 369 | res\drawable-ldpi 370 | 1 371 | 372 | 373 | 374 | 375 | res\drawable-mdpi 376 | 1 377 | 378 | 379 | res\drawable-mdpi 380 | 1 381 | 382 | 383 | 384 | 385 | res\drawable-hdpi 386 | 1 387 | 388 | 389 | res\drawable-hdpi 390 | 1 391 | 392 | 393 | 394 | 395 | res\drawable-xhdpi 396 | 1 397 | 398 | 399 | res\drawable-xhdpi 400 | 1 401 | 402 | 403 | 404 | 405 | res\drawable-mdpi 406 | 1 407 | 408 | 409 | res\drawable-mdpi 410 | 1 411 | 412 | 413 | 414 | 415 | res\drawable-hdpi 416 | 1 417 | 418 | 419 | res\drawable-hdpi 420 | 1 421 | 422 | 423 | 424 | 425 | res\drawable-xhdpi 426 | 1 427 | 428 | 429 | res\drawable-xhdpi 430 | 1 431 | 432 | 433 | 434 | 435 | res\drawable-xxhdpi 436 | 1 437 | 438 | 439 | res\drawable-xxhdpi 440 | 1 441 | 442 | 443 | 444 | 445 | res\drawable-xxxhdpi 446 | 1 447 | 448 | 449 | res\drawable-xxxhdpi 450 | 1 451 | 452 | 453 | 454 | 455 | res\drawable-small 456 | 1 457 | 458 | 459 | res\drawable-small 460 | 1 461 | 462 | 463 | 464 | 465 | res\drawable-normal 466 | 1 467 | 468 | 469 | res\drawable-normal 470 | 1 471 | 472 | 473 | 474 | 475 | res\drawable-large 476 | 1 477 | 478 | 479 | res\drawable-large 480 | 1 481 | 482 | 483 | 484 | 485 | res\drawable-xlarge 486 | 1 487 | 488 | 489 | res\drawable-xlarge 490 | 1 491 | 492 | 493 | 494 | 495 | res\values 496 | 1 497 | 498 | 499 | res\values 500 | 1 501 | 502 | 503 | 504 | 505 | res\drawable-anydpi-v24 506 | 1 507 | 508 | 509 | res\drawable-anydpi-v24 510 | 1 511 | 512 | 513 | 514 | 515 | res\drawable 516 | 1 517 | 518 | 519 | res\drawable 520 | 1 521 | 522 | 523 | 524 | 525 | res\drawable-night-anydpi-v21 526 | 1 527 | 528 | 529 | res\drawable-night-anydpi-v21 530 | 1 531 | 532 | 533 | 534 | 535 | res\drawable-anydpi-v31 536 | 1 537 | 538 | 539 | res\drawable-anydpi-v31 540 | 1 541 | 542 | 543 | 544 | 545 | res\drawable-night-anydpi-v31 546 | 1 547 | 548 | 549 | res\drawable-night-anydpi-v31 550 | 1 551 | 552 | 553 | 554 | 555 | 1 556 | 557 | 558 | Contents\MacOS 559 | 1 560 | 561 | 562 | 0 563 | 564 | 565 | 566 | 567 | Contents\MacOS 568 | 1 569 | .framework 570 | 571 | 572 | Contents\MacOS 573 | 1 574 | .framework 575 | 576 | 577 | Contents\MacOS 578 | 1 579 | .framework 580 | 581 | 582 | 0 583 | 584 | 585 | 586 | 587 | 1 588 | .dylib 589 | 590 | 591 | 1 592 | .dylib 593 | 594 | 595 | 1 596 | .dylib 597 | 598 | 599 | Contents\MacOS 600 | 1 601 | .dylib 602 | 603 | 604 | Contents\MacOS 605 | 1 606 | .dylib 607 | 608 | 609 | Contents\MacOS 610 | 1 611 | .dylib 612 | 613 | 614 | 0 615 | .dll;.bpl 616 | 617 | 618 | 619 | 620 | 1 621 | .dylib 622 | 623 | 624 | 1 625 | .dylib 626 | 627 | 628 | 1 629 | .dylib 630 | 631 | 632 | Contents\MacOS 633 | 1 634 | .dylib 635 | 636 | 637 | Contents\MacOS 638 | 1 639 | .dylib 640 | 641 | 642 | Contents\MacOS 643 | 1 644 | .dylib 645 | 646 | 647 | 0 648 | .bpl 649 | 650 | 651 | 652 | 653 | 0 654 | 655 | 656 | 0 657 | 658 | 659 | 0 660 | 661 | 662 | 0 663 | 664 | 665 | 0 666 | 667 | 668 | Contents\Resources\StartUp\ 669 | 0 670 | 671 | 672 | Contents\Resources\StartUp\ 673 | 0 674 | 675 | 676 | Contents\Resources\StartUp\ 677 | 0 678 | 679 | 680 | 0 681 | 682 | 683 | 684 | 685 | 1 686 | 687 | 688 | 1 689 | 690 | 691 | 692 | 693 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 694 | 1 695 | 696 | 697 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 698 | 1 699 | 700 | 701 | 702 | 703 | ..\ 704 | 1 705 | 706 | 707 | ..\ 708 | 1 709 | 710 | 711 | ..\ 712 | 1 713 | 714 | 715 | 716 | 717 | Contents 718 | 1 719 | 720 | 721 | Contents 722 | 1 723 | 724 | 725 | Contents 726 | 1 727 | 728 | 729 | 730 | 731 | Contents\Resources 732 | 1 733 | 734 | 735 | Contents\Resources 736 | 1 737 | 738 | 739 | Contents\Resources 740 | 1 741 | 742 | 743 | 744 | 745 | library\lib\armeabi-v7a 746 | 1 747 | 748 | 749 | library\lib\arm64-v8a 750 | 1 751 | 752 | 753 | 1 754 | 755 | 756 | 1 757 | 758 | 759 | 1 760 | 761 | 762 | 1 763 | 764 | 765 | Contents\MacOS 766 | 1 767 | 768 | 769 | Contents\MacOS 770 | 1 771 | 772 | 773 | Contents\MacOS 774 | 1 775 | 776 | 777 | 0 778 | 779 | 780 | 781 | 782 | library\lib\armeabi-v7a 783 | 1 784 | 785 | 786 | 787 | 788 | 1 789 | 790 | 791 | 1 792 | 793 | 794 | 1 795 | 796 | 797 | 798 | 799 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 800 | 1 801 | 802 | 803 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 804 | 1 805 | 806 | 807 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 808 | 1 809 | 810 | 811 | 812 | 813 | ..\ 814 | 1 815 | 816 | 817 | ..\ 818 | 1 819 | 820 | 821 | ..\ 822 | 1 823 | 824 | 825 | 826 | 827 | 1 828 | 829 | 830 | 1 831 | 832 | 833 | 1 834 | 835 | 836 | 837 | 838 | ..\$(PROJECTNAME).launchscreen 839 | 64 840 | 841 | 842 | ..\$(PROJECTNAME).launchscreen 843 | 64 844 | 845 | 846 | 847 | 848 | 1 849 | 850 | 851 | 1 852 | 853 | 854 | 1 855 | 856 | 857 | 858 | 859 | Assets 860 | 1 861 | 862 | 863 | Assets 864 | 1 865 | 866 | 867 | 868 | 869 | Assets 870 | 1 871 | 872 | 873 | Assets 874 | 1 875 | 876 | 877 | 878 | 879 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 880 | 1 881 | 882 | 883 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 884 | 1 885 | 886 | 887 | 888 | 889 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 890 | 1 891 | 892 | 893 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 894 | 1 895 | 896 | 897 | 898 | 899 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 900 | 1 901 | 902 | 903 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 904 | 1 905 | 906 | 907 | 908 | 909 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 910 | 1 911 | 912 | 913 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 914 | 1 915 | 916 | 917 | 918 | 919 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 920 | 1 921 | 922 | 923 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 924 | 1 925 | 926 | 927 | 928 | 929 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 930 | 1 931 | 932 | 933 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 934 | 1 935 | 936 | 937 | 938 | 939 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 940 | 1 941 | 942 | 943 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 944 | 1 945 | 946 | 947 | 948 | 949 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 950 | 1 951 | 952 | 953 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 954 | 1 955 | 956 | 957 | 958 | 959 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 960 | 1 961 | 962 | 963 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 964 | 1 965 | 966 | 967 | 968 | 969 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 970 | 1 971 | 972 | 973 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 974 | 1 975 | 976 | 977 | 978 | 979 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 980 | 1 981 | 982 | 983 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 984 | 1 985 | 986 | 987 | 988 | 989 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 990 | 1 991 | 992 | 993 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 994 | 1 995 | 996 | 997 | 998 | 999 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1000 | 1 1001 | 1002 | 1003 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1004 | 1 1005 | 1006 | 1007 | 1008 | 1009 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1010 | 1 1011 | 1012 | 1013 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1014 | 1 1015 | 1016 | 1017 | 1018 | 1019 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1020 | 1 1021 | 1022 | 1023 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1024 | 1 1025 | 1026 | 1027 | 1028 | 1029 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1030 | 1 1031 | 1032 | 1033 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1034 | 1 1035 | 1036 | 1037 | 1038 | 1039 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1040 | 1 1041 | 1042 | 1043 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1044 | 1 1045 | 1046 | 1047 | 1048 | 1049 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1050 | 1 1051 | 1052 | 1053 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1054 | 1 1055 | 1056 | 1057 | 1058 | 1059 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1060 | 1 1061 | 1062 | 1063 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1064 | 1 1065 | 1066 | 1067 | 1068 | 1069 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1070 | 1 1071 | 1072 | 1073 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1074 | 1 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | True 1092 | False 1093 | 1094 | 1095 | S:\tms\tmsbuild\server\test\tmssetupserver_tests.dproj 1096 | 1097 | 1098 | 12 1099 | 1100 | 1101 | 1102 | 1103 |
1104 | -------------------------------------------------------------------------------- /Server/config/local/WinServer.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApiModule": { 3 | "BaseUrl": "http://+:2001/tms/api", 4 | "Middleware": { 5 | "Jwt": { 6 | "ExpectedIssuers": [ 7 | "http://localhost:2001/tms/auth" 8 | ], 9 | "ExpectedAudiences": [ 10 | "http://localhost:2001/tms/api" 11 | ] 12 | } 13 | } 14 | }, 15 | "AuthModule": { 16 | "BaseUrl": "http://+:2001/tms/auth", 17 | "SigningKeyId": "404964ad925b462c891b732813162705" 18 | }, 19 | "Service": { 20 | "Name": "XDataService", 21 | "DisplayName": "TMS XData Local Server", 22 | "Description": "Runs TMS XData API server local debug mode" 23 | }, 24 | "RSAKeysFolder": "..\\..\\config\\local\\keys", 25 | "SecretsFile": "..\\..\\config\\local\\secrets.json" 26 | } -------------------------------------------------------------------------------- /Server/config/local/keys/404964ad925b462c891b732813162705.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAw0nz5kKX6qAa90QLmOs/UNHEDTPGhc6IrZ6+6uPYz+/FIcGo 3 | 2r6CAuSCx7G/Xrb1Vzc5umx1CTKORmr3BX8osXGyXHRYwEDUoCQh19icNbuJg1cw 4 | SgSB6WZwXaAdLzSde3r6ABw5r6vRfrFOrrj3YOFisGmLZfg2p4eF32zyWbDBDNUc 5 | 0UIAf3GTiIUbGDvGB5DKPMjZE3t9yJSZqGErhNd6ptSSmjav8C147/gDtRROFs3O 6 | FlmCJ3532kM8flh9Gzh8CNJwjbvlUTmFiM1w5gDUZfjG4Rm2jphbZjbu0OgftTMd 7 | BNrV00clJdO79pP3clHrNDWxtLtCQ6uualMZAwIDAQABAoIBAHg9LUWFlQGp6FB7 8 | /eP+W/KH5Pz39vBl1LOJfSUkela02xxIn41nJKdhSMBpSVmornAvmYSVyI2MKhtj 9 | OoqQ2149kcLpA8gDYqbi+E9sl+lheW57nUjFc7NgFikfLzjkXV4svoaY2k+2K3uk 10 | 8bwUOIPW4auaoHB9HjGR+LQy4Rik7a+KKkATaynN8x7NVngc71fYAAbLTB/GvG86 11 | 0yUgI23UexvzVWa6Y1G4roqADHOEJnUtxdgPwOVbQXPZSy4D8r6OEWte9BIWIE8f 12 | DISunL0cLysBisL5Namh4HsRuKDoKPhazQmXYWSL1rlbh+2lawGYzcS6kmhO6FUx 13 | KIM+nJkCgYEA+EJDjjRzZkK7yukSADLqPshvwYyWj3gUrFInRfT5p+iyGSYm/nZl 14 | uL9dabqr4BIvJzCP/VvHy/HRWMEobP3cGszQ62EZ1MmRx0acVD0ZSGIVmfm5/S+s 15 | L66x0g3tbXjQhcn254GTtQ7oEbGILf+gBus74sDXq+5wnL7bTSPw168CgYEAyWDa 16 | nqSjVshnt5pDbps4cj+O6yTfeBxJnSqhNWAOnsUs5Je/4twlTonn+DI3eD2d5iJn 17 | PME1OXKgEOlXAv8xqXrnpP+mshQ6i/FUYYiJ5wFqoH07GnC/Yjl0IoXOcJwxjOJm 18 | JH6s8R8jYweqegEYY9K0kBHqjg7PxZn4HctKVO0CgYEAmHdRt+8chEwtuxNi275p 19 | QD+m6u5ltRzhTWdy+TLXdLijUHCu+A5MsKbOPtXHIujoDqBzff00+lq74E9YfX93 20 | AvqQwE41KArXPdRuxAl2Eo8yhwron8b8b5hRWxj0nQvavJ/pxAo/Za4O9nbalK1u 21 | YXFsqPkYO15HyOwqEPkmu78CgYAjXs6nZLWHzaVh+0dyyvKsJnu803oox5cqcs1j 22 | 6Z5HxswuznSWrSIkXFaRIojZpsDwJgTKCAmIMJjqv4FX9mjLotnDAmBOEP2DjPzM 23 | MMY/++C1lalm6HzFDOBYG/Z5kwWlADL+n/1SUKzPfQ41ZbHZ/q1WTL9M+k8GHxgU 24 | a5t/tQKBgQDJmVAz1ingahZuHa4aQJKIrsVq702Yye+gsqkeMDOSVdKdjadvq8km 25 | dR9+cmX/kN8pLc0GVGr4ZbjwzPM5nCCXi56m/Ag5fttWqwp6/Z3A8mS0OkpHaxj3 26 | szlpN31tiGltuaOkZMDMm/596R3to9rWCJxjw2zG7ZmOp63uXIv0JQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /Server/config/local/keys/404964ad925b462c891b732813162705.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAw0nz5kKX6qAa90QLmOs/UNHEDTPGhc6IrZ6+6uPYz+/FIcGo2r6C 3 | AuSCx7G/Xrb1Vzc5umx1CTKORmr3BX8osXGyXHRYwEDUoCQh19icNbuJg1cwSgSB 4 | 6WZwXaAdLzSde3r6ABw5r6vRfrFOrrj3YOFisGmLZfg2p4eF32zyWbDBDNUc0UIA 5 | f3GTiIUbGDvGB5DKPMjZE3t9yJSZqGErhNd6ptSSmjav8C147/gDtRROFs3OFlmC 6 | J3532kM8flh9Gzh8CNJwjbvlUTmFiM1w5gDUZfjG4Rm2jphbZjbu0OgftTMdBNrV 7 | 00clJdO79pP3clHrNDWxtLtCQ6uualMZAwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /Server/config/local/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Clients": [ 3 | { 4 | "ClientName": "Client for local server only. Should not be used in production", 5 | "ClientId": "client123", 6 | "ClientSecret": "secret123", 7 | "Scope": "setup:write setup:read", 8 | "Audiences": [ 9 | "http://localhost:2001/tms/api", 10 | "http://localhost:2001/tms/admin" 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Server/shared/Services.Sample.pas: -------------------------------------------------------------------------------- 1 | unit Services.Sample; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, XData.Service.Common; 8 | 9 | type 10 | /// 11 | /// Sample endpoints. 12 | /// 13 | [ServiceContract] 14 | [Route('sample')] 15 | [ValidateParams] 16 | ISampleService = interface(IInvokable) 17 | ['{4A3455BB-86D5-433C-89DB-5686DA7B567A}'] 18 | 19 | /// 20 | /// Echoes the provided string value. 21 | /// 22 | /// The value provided as parameter. 23 | [HttpGet, Route('echo')] 24 | function EchoString(const Value: string): string; 25 | 26 | /// 27 | /// Adds two numbers A and B. 28 | /// 29 | /// 30 | /// Description for A parameter. Only method documentation, doesn't appear in Swagger. 31 | /// 32 | /// 33 | /// Description for B parameter. Only method documentation, doesn't appear in Swagger. 34 | /// 35 | /// The sum of A ad B. 36 | [HttpGet, Route('add')] 37 | function Add(const A, B: Double): Double; 38 | end; 39 | 40 | implementation 41 | 42 | initialization 43 | RegisterServiceType(TypeInfo(ISampleService)); 44 | 45 | end. 46 | --------------------------------------------------------------------------------