Authentication
A user authentication system with password-based login, session management, and brute-force protection. Demonstrates existential quantifiers, domain functions, and rate limiting.
File: examples/auth.intent
module Authentication
--- User authentication system with password-based login,
--- session management, and brute-force protection.
entity User {
id: UUID
email: Email
password_hash: String
status: Active | Suspended | Deactivated
failed_attempts: Int
locked_until: DateTime?
last_login: DateTime?
created_at: DateTime
}
entity Session {
id: UUID
user: User
token: String
expires_at: DateTime
created_at: DateTime
revoked: Bool
}
action Login {
--- Authenticate a user with email and password.
email: Email
password: String
requires {
exists u: User => u.email == email
lookup(User, email).status == Active
lookup(User, email).locked_until == null ||
lookup(User, email).locked_until < now()
}
ensures {
when password_verified(password, lookup(User, email).password_hash) =>
exists s: Session =>
s.user.email == email &&
s.revoked == false &&
s.expires_at > now()
&&
lookup(User, email).failed_attempts == 0
&&
lookup(User, email).last_login == now()
when !password_verified(password, lookup(User, email).password_hash) =>
lookup(User, email).failed_attempts ==
old(lookup(User, email).failed_attempts) + 1
}
properties {
audit_logged: true
rate_limited: { max: 10, window_seconds: 60, key: email }
sensitive_fields: [password]
}
}
action Logout {
--- End a user session.
session: Session
requires {
session.revoked == false
session.expires_at > now()
}
ensures {
session.revoked == true
}
properties {
idempotent: true
audit_logged: true
}
}
invariant MaxFailedAttempts {
--- Lock accounts after 5 consecutive failed login attempts.
forall u: User =>
u.failed_attempts >= 5 => u.locked_until != null
}
invariant SessionExpiry {
--- All active sessions must have a future expiration.
forall s: Session =>
s.revoked == false => s.expires_at > now()
}
edge_cases {
when lookup(User, email).status == Suspended =>
reject("Account suspended. Contact support.")
when lookup(User, email).status == Deactivated =>
reject("Account deactivated.")
when lookup(User, email).failed_attempts >= 5 =>
reject("Account locked due to too many failed attempts.")
}
Key concepts demonstrated
- Domain functions like
lookup(),now(),password_verified() - Conditional postconditions using
when ... =>in ensures blocks - Existential quantifiers in both requires and ensures
- Implication in invariants (
failed_attempts >= 5 => locked_until != null) - Rate limiting and sensitive fields as action properties