use std::net::IpAddr;
use net_backend_protocol::auth::LinkedIdentity;
use net_backend_protocol::{CloseCode, UserId};
use super::steam::SteamIdentity;
use crate::hooks::Event;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum LoginMethod {
Password,
Steam,
Register,
}
impl LoginMethod {
pub fn as_str(self) -> &'static str {
match self {
LoginMethod::Password => "password",
LoginMethod::Steam => "steam",
LoginMethod::Register => "register",
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct BeforeRegister {
pub email: Option<String>,
pub display_name: Option<String>,
pub identity: Option<LinkedIdentity>,
pub ip: Option<IpAddr>,
}
impl Event for BeforeRegister {
const NAME: &'static str = "auth.before_register";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AfterRegister {
pub user_id: UserId,
pub email: Option<String>,
pub display_name: Option<String>,
pub identity: Option<LinkedIdentity>,
}
impl Event for AfterRegister {
const NAME: &'static str = "auth.after_register";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct BeforeLogin {
pub user_id: UserId,
pub method: LoginMethod,
pub steam: Option<SteamIdentity>,
pub ip: Option<IpAddr>,
}
impl Event for BeforeLogin {
const NAME: &'static str = "auth.before_login";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AfterLogin {
pub user_id: UserId,
pub session_id: i64,
pub method: LoginMethod,
pub ip: Option<IpAddr>,
}
impl Event for AfterLogin {
const NAME: &'static str = "auth.after_login";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct BeforeAccountUpdate {
pub user_id: UserId,
pub display_name: Option<String>,
}
impl Event for BeforeAccountUpdate {
const NAME: &'static str = "auth.before_account_update";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AfterEmailVerified {
pub user_id: UserId,
}
impl Event for AfterEmailVerified {
const NAME: &'static str = "auth.after_email_verified";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AfterPasswordChanged {
pub user_id: UserId,
pub reset: bool,
}
impl Event for AfterPasswordChanged {
const NAME: &'static str = "auth.after_password_changed";
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct AfterSessionsRevoked {
pub revocation: Revocation,
}
impl Event for AfterSessionsRevoked {
const NAME: &'static str = "auth.after_sessions_revoked";
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RevokedSessions {
One(i64),
All,
AllExcept(i64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RevocationReason {
Logout,
PasswordChanged,
PasswordReset,
Banned,
Admin,
RefreshTokenReused,
}
impl RevocationReason {
pub fn as_str(self) -> &'static str {
match self {
RevocationReason::Logout => "logout",
RevocationReason::PasswordChanged => "password_changed",
RevocationReason::PasswordReset => "password_reset",
RevocationReason::Banned => "banned",
RevocationReason::Admin => "admin",
RevocationReason::RefreshTokenReused => "refresh_reused",
}
}
pub fn from_name(name: &str) -> Self {
match name {
"logout" => RevocationReason::Logout,
"password_changed" => RevocationReason::PasswordChanged,
"password_reset" => RevocationReason::PasswordReset,
"banned" => RevocationReason::Banned,
"refresh_reused" => RevocationReason::RefreshTokenReused,
_ => RevocationReason::Admin,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Revocation {
pub user_id: UserId,
pub sessions: RevokedSessions,
pub reason: RevocationReason,
}
impl Revocation {
pub fn new(user_id: UserId, sessions: RevokedSessions, reason: RevocationReason) -> Self {
Self { user_id, sessions, reason }
}
pub fn close_code(&self) -> CloseCode {
match self.reason {
RevocationReason::Banned => CloseCode::BANNED,
_ => CloseCode::UNAUTHORIZED,
}
}
pub fn applies_to(&self, user: UserId, session: Option<i64>) -> bool {
if user != self.user_id {
return false;
}
match (self.sessions, session) {
(RevokedSessions::All, _) => true,
(RevokedSessions::One(id), Some(s)) => id == s,
(RevokedSessions::AllExcept(id), Some(s)) => id != s,
(RevokedSessions::AllExcept(_), None) => true,
(RevokedSessions::One(_), None) => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn revocations() {
let user = UserId(1);
let ban = Revocation::new(user, RevokedSessions::All, RevocationReason::Banned);
assert_eq!(ban.close_code(), CloseCode::BANNED);
assert!(ban.applies_to(user, Some(5)) && ban.applies_to(user, None) && !ban.applies_to(UserId(2), Some(5)));
let one = Revocation::new(user, RevokedSessions::One(5), RevocationReason::Logout);
assert_eq!(one.close_code(), CloseCode::UNAUTHORIZED);
assert!(one.applies_to(user, Some(5)) && !one.applies_to(user, Some(6)) && !one.applies_to(user, None));
let others = Revocation::new(user, RevokedSessions::AllExcept(5), RevocationReason::PasswordChanged);
assert!(!others.applies_to(user, Some(5)) && others.applies_to(user, Some(6)));
assert_eq!(RevocationReason::RefreshTokenReused.as_str(), "refresh_reused");
for reason in [
RevocationReason::Logout,
RevocationReason::PasswordChanged,
RevocationReason::PasswordReset,
RevocationReason::Banned,
RevocationReason::Admin,
RevocationReason::RefreshTokenReused,
] {
assert_eq!(RevocationReason::from_name(reason.as_str()), reason);
}
assert_eq!(LoginMethod::Steam.as_str(), "steam");
}
}