Skip to main content

kcode_k1_users/
lib.rs

1use kcode_k1_accounts::{Account, K1Accounts, NewAccount, PersonId};
2use kcode_k1_invites::{InviteCode, UserId};
3use kcode_k1_persons::{K1Persons, TxId};
4use std::sync::{Arc, Mutex, MutexGuard};
5
6pub struct NewUser {
7    account: NewAccount,
8    full_name: String,
9}
10
11impl NewUser {
12    pub fn new(
13        username: &str,
14        full_name: &str,
15        public_key: [u8; 32],
16        tos_revision: u64,
17        tos_digest: [u8; 32],
18        acceptance_signature: [u8; 64],
19    ) -> Result<Self, String> {
20        let account = NewAccount::new(
21            username,
22            zero_person_id(),
23            public_key,
24            tos_revision,
25            tos_digest,
26            acceptance_signature,
27        )?;
28        validate_full_name(full_name)?;
29        Ok(Self {
30            account,
31            full_name: copy_string(full_name)?,
32        })
33    }
34
35    pub fn username(&self) -> &str {
36        self.account.username()
37    }
38
39    pub fn full_name(&self) -> &str {
40        &self.full_name
41    }
42
43    pub fn public_key(&self) -> [u8; 32] {
44        self.account.public_key()
45    }
46
47    pub fn tos_revision(&self) -> u64 {
48        self.account.tos_revision()
49    }
50
51    pub fn tos_digest(&self) -> [u8; 32] {
52        self.account.tos_digest()
53    }
54
55    pub fn acceptance_signature(&self) -> [u8; 64] {
56        self.account.acceptance_signature()
57    }
58}
59
60pub struct User {
61    account: Account,
62    person_id: PersonId,
63    full_name: String,
64}
65
66impl User {
67    pub fn user_id(&self) -> UserId {
68        self.account.user_id()
69    }
70
71    pub fn username(&self) -> &str {
72        self.account.username()
73    }
74
75    pub fn person_anchor_id(&self) -> PersonId {
76        self.account.person_id()
77    }
78
79    pub fn person_id(&self) -> PersonId {
80        self.person_id
81    }
82
83    pub fn full_name(&self) -> &str {
84        &self.full_name
85    }
86
87    pub fn public_key(&self) -> [u8; 32] {
88        self.account.public_key()
89    }
90
91    pub fn tos_revision(&self) -> u64 {
92        self.account.tos_revision()
93    }
94
95    pub fn tos_digest(&self) -> [u8; 32] {
96        self.account.tos_digest()
97    }
98
99    pub fn acceptance_signature(&self) -> [u8; 64] {
100        self.account.acceptance_signature()
101    }
102}
103
104pub struct K1Users {
105    accounts: Arc<K1Accounts>,
106    persons: Arc<K1Persons>,
107    registrations: Mutex<()>,
108}
109
110impl K1Users {
111    pub fn new(accounts: Arc<K1Accounts>, persons: Arc<K1Persons>) -> Self {
112        Self {
113            accounts,
114            persons,
115            registrations: Mutex::new(()),
116        }
117    }
118
119    pub fn register(&self, code: &InviteCode, user: NewUser) -> Result<User, String> {
120        let _registration = lock_unpoison(&self.registrations);
121        if let Some(account) = self.accounts.find_by_username(user.username())? {
122            if !account_matches(&account, &user) {
123                return Err("username already registered".to_owned());
124            }
125            return self.resolve_user(account);
126        }
127        let NewUser { account, full_name } = user;
128        let person_id = self.persons.create(full_name)?;
129        let account = NewAccount::new(
130            account.username(),
131            person_id,
132            account.public_key(),
133            account.tos_revision(),
134            account.tos_digest(),
135            account.acceptance_signature(),
136        )?;
137        let account = self.accounts.register(code, account)?;
138        self.resolve_user(account)
139    }
140
141    pub fn get(&self, user_id: UserId) -> Result<Option<User>, String> {
142        self.accounts
143            .get(user_id)?
144            .map(|account| self.resolve_user(account))
145            .transpose()
146    }
147
148    pub fn find_by_username(&self, username: &str) -> Result<Option<User>, String> {
149        self.accounts
150            .find_by_username(username)?
151            .map(|account| self.resolve_user(account))
152            .transpose()
153    }
154
155    fn resolve_user(&self, account: Account) -> Result<User, String> {
156        let view = self
157            .persons
158            .read(account.person_id())?
159            .ok_or_else(|| "account person anchor is missing".to_owned())?;
160        Ok(User {
161            account,
162            person_id: view.person_id,
163            full_name: view.name,
164        })
165    }
166}
167
168fn account_matches(account: &Account, user: &NewUser) -> bool {
169    account.username() == user.username()
170        && account.public_key() == user.public_key()
171        && account.tos_revision() == user.tos_revision()
172        && account.tos_digest() == user.tos_digest()
173        && account.acceptance_signature() == user.acceptance_signature()
174}
175
176fn validate_full_name(full_name: &str) -> Result<(), String> {
177    if !(1..=128).contains(&full_name.len()) {
178        return Err("person name must be 1 through 128 UTF-8 bytes".to_owned());
179    }
180    if full_name.chars().any(char::is_control) {
181        return Err("person name must not contain control characters".to_owned());
182    }
183    if full_name.chars().all(char::is_whitespace) {
184        return Err("person name must contain a non-whitespace character".to_owned());
185    }
186    Ok(())
187}
188
189fn copy_string(value: &str) -> Result<String, String> {
190    let mut copy = String::new();
191    copy.try_reserve_exact(value.len())
192        .map_err(|_| "memory allocation failed".to_owned())?;
193    copy.push_str(value);
194    Ok(copy)
195}
196
197fn zero_person_id() -> PersonId {
198    PersonId::from_tx_id(TxId::from_bytes([0; 12]))
199}
200
201fn lock_unpoison<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
202    match mutex.lock() {
203        Ok(guard) => guard,
204        Err(poisoned) => poisoned.into_inner(),
205    }
206}