Authenticatie Reducers
Reducers voor speler registratie en login.
Overzicht
Het authenticatiesysteem gebruikt:
- SpacetimeDB Identity - Unieke client identifier
- PIN codes - 6-cijferige codes voor account recovery
- Device linking - Accounts koppelen aan nieuwe devices
Reducers
register_player
Registreer een nieuwe speler (zonder PIN).
#[reducer]
pub fn register_player(
ctx: &ReducerContext,
username: String,
display_name: String,
) -> Result<(), String>
Parameters:
| Param | Type | Beschrijving |
|---|---|---|
| username | String | Unieke gebruikersnaam (3-20 chars) |
| display_name | String | Weergavenaam |
Validatie:
- Username 3-20 karakters
- Username nog niet in gebruik
- Identity nog niet geregistreerd
Voorbeeld (GDScript):
client.reducers.register_player("milena123", "Milena")
register_player_with_pin
Registreer met een PIN code voor account recovery.
#[reducer]
pub fn register_player_with_pin(
ctx: &ReducerContext,
username: String,
display_name: String,
pin: String,
) -> Result<(), String>
Parameters:
| Param | Type | Beschrijving |
|---|---|---|
| username | String | Unieke gebruikersnaam |
| display_name | String | Weergavenaam |
| pin | String | 6-cijferige PIN |
Validatie:
- Alle register_player validaties
- PIN exact 6 cijfers
Wat er gebeurt:
- Player record aangemaakt
- PlayerCredentials met PIN hash opgeslagen
- Initiële PlayerPosition gezet (treehouse entrance)
// Initiële positie
ctx.db.player_position().insert(PlayerPosition {
identity,
scene: "treehouse".to_string(),
x: 576.0,
y: 500.0,
direction: 0,
is_moving: false,
updated_at: now,
});
login_with_pin
Login op nieuw device met bestaand account.
#[reducer]
pub fn login_with_pin(
ctx: &ReducerContext,
username: String,
pin: String,
) -> Result<(), String>
Use case: Speler heeft nieuw device/browser en wil bestaand account gebruiken.
Wat er gebeurt:
- Credentials ophalen voor username
- PIN hash vergelijken
- Identity migratie:
- Oude player record verwijderen
- Nieuwe player record met zelfde data + nieuwe identity
- Position data kopiëren
- Credentials updaten naar nieuwe identity
// Identity migratie
let old_identity = creds.linked_identity;
let new_identity = ctx.sender;
// Delete old, create new with same data
ctx.db.player().identity().delete(old_identity);
ctx.db.player().insert(Player {
identity: new_identity,
username: username.clone(),
// ... rest van data behouden
});
Errors:
| Error | Oorzaak |
|---|---|
| "This device is already registered" | Identity heeft al een player |
| "Username not found" | Username bestaat niet |
| "Incorrect PIN" | PIN hash matcht niet |
update_character
Update karakter customization.
#[reducer]
pub fn update_character(
ctx: &ReducerContext,
skin_color: u8,
hair_style: u8,
hair_color: u8,
outfit: u8,
accessory: u8,
) -> Result<(), String>
Parameters:
| Param | Type | Beschrijving |
|---|---|---|
| skin_color | u8 | Skin kleur index |
| hair_style | u8 | Haar stijl index |
| hair_color | u8 | Haar kleur index |
| outfit | u8 | Outfit index |
| accessory | u8 | Accessoire index |
Voorbeeld:
client.reducers.update_character(
2, # skin_color
5, # hair_style
1, # hair_color
3, # outfit
0 # accessory
)
PIN Hashing
De PIN wordt gehashed met een simpele hash (niet cryptografisch):
fn hash_pin(pin: &str) -> String {
let mut hash: u64 = 5381;
for c in pin.chars() {
hash = hash.wrapping_mul(33).wrapping_add(c as u64);
}
format!("{:016x}", hash)
}
Note: Dit is voldoende voor een kinderspel. Voor productie met gevoelige data, gebruik bcrypt of argon2.
Flow Diagrammen
Nieuwe Speler
Client Server
│ │
│ register_player_with_pin │
│ (username, name, pin) │
│──────────────────────────────▶│
│ │ Validate
│ │ Create Player
│ │ Create Credentials
│ │ Create Position
│ OK │
│◀──────────────────────────────│
Login op Nieuw Device
Device A (oud) Server Device B (nieuw)
│ │ │
│ (heeft identity_A) │ │
│ │ │
│ │ login_with_pin │
│ │ (username, pin) │
│ │◀───────────────────────│
│ │ │
│ │ Verify PIN │
│ │ Migrate identity │
│ │ A → B │
│ │ │
│ (sessie invalid) │ OK │
│◀──────────────────────────│───────────────────────▶│
│ │ (nu eigenaar) │
Best Practices
Username Validatie
// In reducer
if username.len() < 3 || username.len() > 20 {
return Err("Username must be 3-20 characters".to_string());
}
// Alleen alphanumeriek + underscore
if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err("Invalid characters in username".to_string());
}
Client-side Pre-validatie
func _on_register_pressed() -> void:
var username = username_input.text.strip_edges()
# Pre-validatie (snellere feedback)
if username.length() < 3:
show_error("Gebruikersnaam te kort")
return
if username.length() > 20:
show_error("Gebruikersnaam te lang")
return
# Server call
var result = await client.call_reducer("register_player", [username, display_name])
if result.status == "failed":
show_error(result.message)