Systems
PasswordSystem
Hash and verify passwords with Argon2id, scrypt or PBKDF2, check password rules, generate passwords and PINs, and protect doors, rooms or admin panels with locks that limit guessing.
Getting it
local PasswordSystem = Engine:GetSystem("PasswordSystem")Overview
Some games let players set passwords: private rooms, club houses, safes, admin panels. PasswordSystem makes sure those passwords are never stored in plain text and cannot be guessed quickly.
- Hashing with Argon2id by default (scrypt and PBKDF2 also available). A hash is a single string in the standard PHC format (
$argon2id$v=19$m=...) that contains the algorithm, the settings and a random salt, so you can store it anywhere, such as a DataService profile. - Policies that check length, character classes and a blocklist.
- Locks: a password-protected thing with built-in attempt limits and lockouts per player.
It runs on the server only and uses CryptographyService underneath. Hashing is deliberately slow, so prefer the Async methods, which do the work without freezing the server.
local PasswordSystem = Engine:GetSystem("PasswordSystem")
-- When the owner sets a password
PasswordSystem:HashAsync(newPassword):andThen(function(hash)
roomProfile:Set("PasswordHash", hash)
end)
-- When someone tries to enter
local lock = PasswordSystem:CreateLock("Room_" .. roomId, roomProfile:Get("PasswordHash"))
enterRemote.Received:Connect(function(player, args)
lock:VerifyAsync(args[1], player):andThen(function(ok, reason)
if ok then
teleportIntoRoom(player)
elseif reason == "LockedOut" then
notify(player, "Too many tries, wait a minute.")
else
notify(player, "Wrong password.")
end
end)
end)Hashing
| Method | Description |
|---|---|
:HashAsync(password, options?): Promise<string> | Hashes a password with a new random salt. |
:Hash(password, options?): string | The same, blocking. |
:VerifyAsync(password, hash): Promise<boolean> | Checks a password against a stored hash in constant time. |
:Verify(password, hash): boolean | The same, blocking. |
:NeedsRehash(hash, options?): boolean | Whether a hash uses older settings than the current ones. After a successful login, hash the password again and store the new hash. |
:Parse(hash): Parsed? | { Algorithm, Parameters, Salt, Hash }, or nil if it is not a valid hash. |
:IsHash(value): boolean |
Hash options override the configuration for one call: Algorithm ("Argon2id", "Argon2i", "Argon2d", "Scrypt", "PBKDF2"), SaltSize, HashSize, and the cost settings Argon2Memory, Argon2Iterations, Argon2Parallelism, ScryptN, ScryptR, ScryptP, Pbkdf2Iterations.
Passwords longer than MaxLength are rejected everywhere, to stop very long inputs being used to slow the server down.
Password rules
| Method | Description |
|---|---|
:Validate(password, policy?): (boolean, reason?) | Checks a new password against the rules. |
:GetPolicy(policy?) | The effective rules, with your overrides applied. |
Reasons (PasswordSystem.PolicyReasons): "TooShort", "TooLong", "NoLetter", "NoDigit", "NoSymbol", "NoMixedCase", "Blocked". Policy overrides: MinLength, MaxLength, RequireLetter, RequireDigit, RequireSymbol, RequireMixedCase, Blocklist (compared case-insensitively).
local ok, reason = PasswordSystem:Validate(input, { MinLength = 6, Blocklist = { "password", "123456" } })Generating
| Method | Description |
|---|---|
:Generate(length?, alphabet?) | A random password from a cryptographically secure source. The default alphabet leaves out look-alike characters. |
:GeneratePin(digits?) | A numeric PIN, such as a safe code. |
Locks
A lock combines a stored hash with attempt limiting. Attempts are counted per key: usually the player, but any string or number works (for example a team).
| Method | Description |
|---|---|
:CreateLock(name, hash, options?): Lock | A lock for an existing hash. |
:CreateLockAsync(name, password, options?): Promise<Lock> | Hashes the password and creates the lock. |
:GetLock(name), :FindLock(name), :GetLocks(), :DestroyLock(name) | |
LockCreated: Signal<Lock>, LockDestroyed: Signal<string> |
Options: MaxAttempts (wrong tries before lockout; 0 never locks out), AttemptWindow (seconds after which wrong tries are forgotten), LockoutSeconds.
| Lock member | Description |
|---|---|
:VerifyAsync(password, key?): Promise<(boolean, reason?)> | Tries a password. Reasons: "Wrong", "LockedOut", or "Busy" while a previous try for the same key is still being checked. |
:Verify(password, key?): (boolean, reason?) | The same, blocking. |
:SetPasswordAsync(password) | Changes the password. |
Hash | The stored hash. Can be replaced directly. |
:GetAttempts(key), :IsLockedOut(key): (boolean, secondsLeft) | |
:Reset(key?) | Clears attempts for one key, or all. |
:Destroy() | |
Unlocked: Signal<key>, Failed: Signal<(key, attempts)>, LockedOut: Signal<(key, seconds)> | |
MaxAttempts, AttemptWindow, LockoutSeconds | Settable. |
Configuration
DefaultConfiguration/Systems/PasswordSystem23 settings| Setting | Default | What it does |
|---|---|---|
| Algorithm | "Argon2id" | Password hash algorithm used when options pass none. |
| SaltSize | 16 | Random salt bytes per password hash. |
| HashSize | 32 | Output bytes per password hash. |
| Argon2Memory | 19456 | KiB of memory per Argon2 password hash. |
| Argon2Iterations | 2 | Passes over that memory per Argon2 password hash. |
| Argon2Parallelism | 1 | Parallel lanes per Argon2 password hash. |
| ScryptN | 16384 | scrypt CPU/memory cost parameter (a power of two). |
| ScryptR | 8 | scrypt block size factor. |
| ScryptP | 1 | scrypt parallelism factor. |
| Pbkdf2Iterations | 100000 | PBKDF2-HMAC-SHA256 iterations. |
| MinLength | 8 | Shortest password :Validate accepts, in characters. |
| MaxLength | 128 | Longest password accepted anywhere. |
| RequireLetter | false | :Validate rejects a password without a letter. |
| RequireDigit | false | :Validate rejects a password without a digit. |
| RequireSymbol | false | :Validate rejects a password without a symbol. |
| RequireMixedCase | false | :Validate rejects a password without both an uppercase and a lowercase letter. |
| Blocklist | {} | Passwords :Validate always rejects, compared case-insensitively. |
| GenerateLength | 16 | Characters in a :Generate password when no length is passed. |
| GenerateAlphabet | "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789" | Characters :Generate draws from when no alphabet is passed. |
| PinDigits | 6 | Digits in a :GeneratePin code when no count is passed. |
| MaxAttempts | 5 | Wrong tries per key before a Lock locks it out; 0 never locks out. |
| AttemptWindow | 300 | Seconds after which a key's wrong tries on a Lock are forgotten. |
| LockoutSeconds | 60 | Seconds a locked-out key waits before a Lock takes its tries again. |
Types
export type Bytes = buffer | string
export type Algorithm = "Argon2id" | "Argon2i" | "Argon2d" | "Scrypt" | "PBKDF2"
export type HashOptions
export type Parsed
export type Policy
export type PolicyOptions
export type PolicyReason
export type Lock
export type LockOptions
export type LockKey = Player | string | number
export type VerifyReason = "Wrong" | "LockedOut" | "Busy"