Ga naar hoofdinhoud

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:

ParamTypeBeschrijving
usernameStringUnieke gebruikersnaam (3-20 chars)
display_nameStringWeergavenaam

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:

ParamTypeBeschrijving
usernameStringUnieke gebruikersnaam
display_nameStringWeergavenaam
pinString6-cijferige PIN

Validatie:

  • Alle register_player validaties
  • PIN exact 6 cijfers

Wat er gebeurt:

  1. Player record aangemaakt
  2. PlayerCredentials met PIN hash opgeslagen
  3. 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:

  1. Credentials ophalen voor username
  2. PIN hash vergelijken
  3. 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:

ErrorOorzaak
"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:

ParamTypeBeschrijving
skin_coloru8Skin kleur index
hair_styleu8Haar stijl index
hair_coloru8Haar kleur index
outfitu8Outfit index
accessoryu8Accessoire 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)

Volgende