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}