User Guide Contributor
  • Content Count

  • Joined

  • Last visited

  • Days Won


NanoBob last won the day on November 28 2018

NanoBob had the most liked content!

Community Reputation

44 Good

1 Follower

About NanoBob

  • Rank
    User Guide Contributor
  • Birthday 25/09/1998


  • Location
    The Netherlands
  • Occupation
    SAES:RPG Development manager
  • Interests
    Software development

Recent Profile Visitors

839 profile views
  1. A new version of the CLI and core have been released! RPCs RPCs being split up into two seperate classes was a bit unintuitive. So we've combined them into a single class. More information on how RPCs work now on Slipe WPF We've released our first "first party module". Slipe WPF! Slipe WPF allows you to create Windows Presentation Foundation (WPF) user interfaces in Visual studio, and then run these in MTA. Warning Slipe WPF is even more alpha than Slipe itself. So no guarantees that it will be bug free. Information on setting up WPF can be found on: A sample resource using WPF can be found on: Updating In order to update your CLI run `slipe update` In order to update an existing project use `slipe update-core`
  2. A new version of the CLI and core have been released! Updating from dev Since Slipe is still in development and we can't always make releases to hotfix an issue we've added the ability to update your CLI and project to the current development branch of the repo. What this means is you will be running the latest version, however these versions are more prone to bugs / unfinished features. In order to update the CLI from the development branch use: `slipe update -dev` In order to update a project from the development branch use: `slipe update-core -dev` Element data sigh, we've implemented element data. We still recommend people don't use it if you can. The only reason we implemented it is to support compatibility with non-slipe resources or mapping files. Bugs We've fixed several bugs, including some important ones related to events. Updating In order to update your CLI run `slipe update` In order to update an existing project use `slipe update-core` Also, update your .net core 3.0 to the most recent preview (preview 7)
  3. Introduction Properly handling your user's credentials (username and password) is very important, this guide gives detailed information and code samples on how to (properly) implement an account "system". This guide assumes you are not using MTA's built in accounts. Disclaimer: Any code shown in this tutorial is purely for illustrative purposes, and is not finished code, you should use it as a guideline, not a solution. Content The following topics will be discussed in this tutorial: How to hash and salt passwords (register) How to validate a hashed password (login) How to add "remember-me" functionality How to offer password recovery How to migrate from an older hashing algorithm, to a newer one Using a password policy (Extra) How to handle database leaks (Extra) What even is hashing and salting? For the purpose of this tutorial we expect a database structure which is somewhat similar to this: How to hash and salt passwords When you have a user register on your service, that user expects of you to keep their password safe. Whilst it is generally bad practice to use the same password for multiple services there are many users that still do so. Because of this it's crucial that you save the user's passwords in a way that an attacker will be unable to find out the original password the user typed. This includes if they have full access to your database. In order to do this we do what is called "Password hashing" When a user registers, your server receives the user's intended username, (email) and password. Before you save that password to the database you have to hash and salt this, luckily MTA has a function that takes care of this. If you wish to know more about what exactly it does, there's more information at the end of this tutorial. In order to hash this password you use the passwordHash function. This function is relatively slow (by design), so it is highly recommended you pass a callback to this function, so your entire script doesn't wait for it to complete. local mysqlHandle -- we're assuming this value is set somewhere else in code function register(username, email, password) local player = client passwordHash(password, "bcrypt", {}, function(hashedPassword) -- callback function for hashing the password local handle = dbExec(function(handle) -- callback function for storing the user in the database if (handle) then triggerClientEvent(player, "registrationSuccess") -- inform the user that registration was successful else triggerClientEvent(player, "registrationFailed") end end,mysqlHandle, "INSERT INTO users (email, username, password) VALUES (?, ?, ?)", email, username, hashedPassword) end) end addEvent("passwordTutorial:register", true) addEventHandler("passwordTutorial:register", getRootElement(), register) How to validate a hashed password Once you've saved the hashed password to your database you need to do a little bit of additional work when authenticating the user. Luckily MTA offers a passwordVerify() function, which is the counterpart of the previously discussed passwordHash(). What this function does it basically hashes the password in the same way, resulting in the same output hash. In order to get the account the user is trying to log in to you have to do a query for an account which has the user submitted username, and of which the password matches through passwordVerify. PasswordVerify is also a relatively slow function, thus you should use a callback. function login(username, password) local player = client dbQuery(function (handle) -- callback for the query selecting the user by username local results = dbPoll(handle, -1) if (#results == 0) then triggerClientEvent(player, "loginFailed") return end passwordVerify(password, results[1].password, {}, function(matches) -- callback function for the password verify if (matches) then -- Do anything you wish with the database result to log the player in with the rest of your scripts triggerClientEvent(player, "loginSuccess") else triggerClientEvent(player, "loginFailed") end end) end, mysqlHandle, "SELECT * FROM users WHERE username = ?", username) end addEvent("passwordTutorial:login", true) addEventHandler("passwordTutorial:login", getRootElement(), login) How to add "remember me" functionality When users on your server log in, they often do not want to have to enter their username and password every time they want to log in. In order to satisfy this need you can implement a "remember me" function. What I've seen happen in the past, is people would store the user's password (encrypted) on the client. This is NOT safe, and should never be done! In order to properly use remember me functionality what you would do is upon login in, generate a random string. The longer the better. This random string is what we call an access token. You would then allow the user to log in with such an access token, preferably only once generating a new access token each time one is used. To implement this you would generate that token every time the user logs in, whilst they have "remember me" enabled. You will have to save this token in your database alongside your user. For extra security you could also store the user's serial alongside the access token, you can then validate that the access token is being used from the same device. function login(username, password) -- This code should be put in the callback to the dbQuery function, but to keep the example clean that's not shown here if (rememberMe) then local token = generateRandomToken() dbExec(function() triggerClientEvent(player, "loginSuccess", token) end,mysqlHandle, "INSERT INTO access_tokens (user_id, token) VALUES (?, ?)", results[1].id, token) end end function rememberMeLogin(username, accessToken) -- this function handles a user's login attempt dbQuery(function(handle) local result = dbPoll(handle, -1) if (#result == 0) then triggerClientEvent(player, "loginFailed") else -- Do anything you wish with the database result to log the player in with the rest of your scripts triggerClientEvent(player, "loginSuccess") end end,mysqlHandle, "SELECT users.* FROM access_tokens JOIN users ON = access_tokens.user_id WHERE users.username = ?", username) end addEvent("passwordTutorial:loginRememberMe", true) addEventHandler("passwordTutorial:loginRememberMe", getRootElement(), login) How to offer password recovery Offering password recovery requires a little bit more than just your MTA server. Generally password recovery is done with emails. So you would need access to an email server / service which you can use to send an email from an HTTP request. (Like you can do with fetchRemote()). When a user requests a password reset, have them enter the email you registered with. You then fetch a user from the database with this email address. You would then store a password recovery token for this user. This token, just like the remember me token, is a random string. Ideally, you would send the user a link with a password reset form that goes to a webpage where the user can reset their password. You could do this with an external service, like a webserver. Or you could use MTA's Resource web access for it, but if you do make sure you handle permissions properly for anything else that uses this. However another option would be to have the user copy paste the generated token from the email into you server's login window. Which of the two solutions you pick is up to you, my personal preference goes to the one with the link in the email. But in either case the server side logic is the same. When the user attempts to perform password recovery, verify that the token they give you belongs to a user, and then change the password to the newly requested password. Make sure you hash this password the same way you do in your login. function requestPasswordRecovery(email) dbQuery(function (handle)) local result = dbPoll(handle, -1) if (#result == 0) then triggerClientEvent(player, "passwordTutorial:passwordRecoveryRequestFailed") else local token = generateRandomToken() dbExec(mysqlHandle, "UPDATE user_data SET recovery_token = ?", token) -- mail the token to the user, mail implementation depends on the mail server/service you use triggerClientEvent(player, "passwordTutorial:passwordRecoveryRequestSuccess") end end, mysqlHandle, "SELECT * FROM users WHERE email = ?", email) end function recoverPassword(recoveryToken, password) dbQuery(function (handle) local result = dbPoll(handle, -1) if (#result == 0) then -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoveryFailed") else passwordHash(password, "bcrypt", {}, function(hashedPassword) -- callback function for hashing the password local handle = dbExec(function(handle) -- callback function for storing the new password in the database if (handle) then -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoverySuccess") -- inform the user that registration was successful else -- this is only valid if you have the user request password recovery from ingame triggerClientEvent(player, "passwordTutorial:passwordRecoveryFailed") end end,mysqlHandle, "UPDATE user_data SET password = ? WHERE recovery_token = ?", username, recoveryToken) end) end end, "SELECT * FROM users WHERE recovery_token = ?", recoveryToken) end Besides changing the password, it's important you also delete any access tokens that user might have if you're using remember me functionality. It is also good practice to make recovery tokens expiry after a certain amount of times, and not allow a recovery token to be created whilst one is already in progress. This prevents a user from sending a large number of emails from your service. How to migrate from an older hashing algorithm, to a newer one Maybe after reading this topic you realise that your password security is not what it should be. So you want to change your old password hashing / validation logic to the ones explained in this topic. And due to the nature that hashes can not be "unhashed", you can't simply migrate your passwords over. So in order to migrate the passwords what you have to do is when a user logs in, first validate their password with the old hashing algorithm. If this matches, then hash (and salt) it with your new hashing algorithm and save it in your database. Make sure to delete the old password otherwise your password security is not any better than before. Using a password policy Passwords policies are important to prevent your users from picking a password that is too easily cracked / brute forced. Many password policies come in the form of "Must have at least one capital letter, one digit and one number". But that discards that fact that the best way to make your password more difficult to crack, is making your password longer. So in the code snippet below is a function that measures the 'search space' of a password. The search space of a password is the amount of possible passwords there are with a certain combination of characters. In order to use this, you would have to set a minimum password search space when a user registers for an account. This minimum is up for you to set, but be reasonable, you shouldn't expect a user's password to be impossible to remember / create. I recommend playing with the function a bit to see what values you get out of it, and pick something you believe is sensible. function getPasswordSearchSpace(password) local lowerCase = password:find("%l") and 26 or 0 local upperCase = password:find("%u") and 26 or 0 local digits = password:find("%d") and 10 or 0 local symbols = password:find("%W") and 32 or 0 local length = password:len() return (lowerCase + upperCase + digits + symbols) ^ length end -- The below function calls are to indicate the difference in search space for a set of passwords print(getPasswordSearchSpace("a")) print(getPasswordSearchSpace("abc")) print(getPasswordSearchSpace("Abc")) print(getPasswordSearchSpace("Ab!")) print(getPasswordSearchSpace("Ab!0")) print(getPasswordSearchSpace("Mu#9A0h.")) print(getPasswordSearchSpace("This is a demonstration of how easy an incredibly strong password is to remember")) How to handle database leaks If you have reason to believe that your database has been leaked or otherwise compromised, it is important that your first course of action is removing any access tokens stored in your database. Once you have done that you have to inform your users. Whilst when properly hashed and salted it's extremely difficult / time consuming to find out a user's password it is still a possibility. So you should inform your users of the breach, tell them that their passwords were properly hashed, and they do not need to fear for their passwords immediately. However you should suggest to your users that they change their password either way, just in case. What even is hashing and salting? Hashing has been brought up several times in this tutorial, whilst you do not need to know what it is / does, you might be interested in knowing regardless. I won't be going too far in depth as I simply do not have the knowledge, but the basic idea of hashing is this: When you hash anything, you turn it into a string of characters (or other value) that has no relation to the original input, other than when you hash the original input again, it will always generate the same hash. For example, when you hash the string 'banana' using the sha512 hashing algorithm, it will always yield the output: "F8E3183D38E6C51889582CB260AB825252F395B4AC8FB0E6B13E9A71F7C10A80D5301E4A949F2783CB0C20205F1D850F87045F4420AD2271C8FD5F0CD8944BE3" Now hashing can not be reverted, you can not "unhash" a hash, so in order to verify someone's password you hash it again, and see if the two hashes are the exact same. Now this is great, passwords are safely stored. However there is still more to do, salting. Salting is adding some random data to your password prior to hashing it. This prevents when two users (on the same service, or on others) have the same password, that their hashes are also the same. Meaning if one password is compromised, the other password is not. It is important that a salt is random for every user in your application, not one salt for your entire application. Now you might think we didn't do any salting in the code / tutorial above. This is not true, we just didn't do it ourselves. MTA's passwordHash function actually hashes the passwords and salts it, this salt is then stored in the output hash it self, just before the actual password hash. In the case of bcrypt it actually stores a little bit more info in the resulting hash, but you need not worry about that.
  4. That would also be rather insecure Soapbosnia, as you would be storing it in recoverable form on the client. (Aka plaintext or encrypted). So all someone would need to do is access the file in order to gain access to the password. When using a token even if that token is compromised the actual password is still secure. Plus you can add a serial restriction on it (save serial per token). That way the token can't be stolen and used from a different device.
  5. A new version of the CLI has been released! Github webhooks The slipe CLI now has built in support for automatically deploying a Slipe resource using github webhook using the `slipe hook` command. More information on how to set it up can be found on Updating In order to update your CLI run `slipe update`
  6. A new version of both the CLI and the core have been released! Changes several bugfixes C# reflection is now supported due to metadata being included in compilation Updating In order to update your CLI run `slipe update` In order to update your projects run `slipe update-core` in the project directory Infrastructure From this point on the Slipe website is hosted in a datacenter (instead of a PC at my home) We have also set up a continuous integration / deployment environment so the downloads on the website will always be up to date.
  7. A new version of both the CLI and the core have been released! Events Events have undergone a major refactor, all events now have a `source` and an `eventArgs` parameter. This will allow us to add parameters to events without breaking backwards compatibility. This also aligns more with C# convention. Vehicles The vehicle "lookup" classes have been changed to reflect vehicle types. Exports It is now possible to export static methods from your Slipe resource. For more information on how to do so visit Updating In order to update the CLI use `slipe update` In order to update an existing project use `slipe update-core` Also we would like to thank those who contribute to Slipe, you guys are awesome!
  8. We have not yet run benchmarks comparing "regular" Lua and Slipe, but we're planning on doing so in the near future. But by inspecting the generated Lua code and the class library the overhead should not be much more (if any) than a "plain" Lua OO implementation.
  9. It is not, sadly. This is due to the fact that all C# has has to be turned into Lua code in order to be run. With a NuGet package you only get the compiled DLL. If the package is open source however (And if it only uses C# classes / libraries that are implemented in slipe) then you should be able to compile it from the source using Slipe. But keep in mind that only very basic C# libraries are currently implemented in Slipe (and many only partially).
  10. The benefit of doing this isn't necesarily performance, but it's the benefits that a strongly typed language such as C# brings with it. But also the tooling that you're able to use for C#, Visual Studio's intellisense especially is great to speed up your development time. It is, because in the end this project just generates Lua code. And MTA events are implemented in our wrapper as C# events. An example of this can be found in the code snippet on the front page of
  11. It's basically both. It does allow you to write C# for MTA:SA, and it does this by translating that C# code to Lua.
  12. A new version of both the CLI and the core have been released! Root Events Events which you would want to handle on the rootElement (For example onPlayerJoin) have now been implemented as static events on that class. For example `Player.OnJoin` Defining default classes If you wish to extend a Slipe class and make it the default class for any element of that type you can do so now using the `DefaultElementClass` attribute, you can read more about that on New updater The previous updater for the CLI was a bit crap, so the windows installer has been redone as a standalone .exe, you can download this at After that you should be able to use `slipe update` like should have been (but wasn't) possible with the previous updater. Updating an existing project? After updating your CLI you can update your existing Slipe projects using `slipe update-core` in the root directory of the project.
  13. Here's a little teaser of a work in progress first party module for Slipe, called SlipeWPF. For those of you unaware of WPF, it's a GUI framework which allows you to create windows desktop applications. We're also bringing this to MTA. This window, created in Visual studio, will turn into: And all it requires to render it in MTA is this little bit of code: MainWindow window = new MainWindow(); Window guiWindow = CeguiWpfRenderer.Render(window);
  14. The Slipe CLI tool has been updated today. New features include: A `slipe build` command to produce a deployment ready resource, including a `-luac` option to automatically compile the lua files using the MTA compiler. Self updating functionality, no longer will you need to manually update the CLI or core after this update. Exporting and import of modules. Your project can exist of seperate smaller modules, which can be exported and shared with others in the Slipe community. For more information on the CLI visit the documentation on
  15. I believe what you're referring to are CLEO mods, whilst CLEO does use the .cs file extension it's not C#, so no that wouldn't work.