summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2024-10-19 01:51:30 -0400
committerOwen Jacobson <owen@grimoire.ca>2024-10-21 00:49:05 -0400
commit379e97c2cb145bc3a495aa14746273d83b508214 (patch)
tree218bbe2572af9dd4b165ff05495d084dc0bd8905
parent98af8ff80da919a1126ba7c6afa65e6654b5ecde (diff)
Unicode normalization on input.
This normalizes the following values: * login names * passwords * channel names * message bodies, because why not The goal here is to have a canonical representation of these values, so that, for example, the service does not inadvertently host two channels whose names are semantically identical but differ in the specifics of how diacritics are encoded, or two users whose names are identical. Normalization is done on input from the wire, using Serde hooks, and when reading from the database. The `crate::nfc::String` type implements these normalizations (as well as normalizing whenever converted from a `std::string::String` generally). This change does not cover: * Trying to cope with passwords that were created as non-normalized strings, which are now non-verifiable as all the paths to verify passwords normalize the input. * Trying to ensure that non-normalized data in the database compares reasonably to normalized data. Fortunately, we don't _do_ very many string comparisons (I think only login names), so this isn't a huge deal at this stage. Login names will probably have to Get Fixed later on, when we figure out how to handle case folding for login name verification.
-rw-r--r--.sqlx/query-0caf898537dd38eea00322a9eb3578281dc70941faf05d53f8525034446ae52f.json (renamed from .sqlx/query-8d9b83fb53e4191ee58b68e65e101d0941f2a266679209f793fd27466b9e3719.json)6
-rw-r--r--.sqlx/query-0f0e4a6ac32b39f3bd7f4832389259b91bbffa182e32b224635031eead2fa82d.json (renamed from .sqlx/query-836a37fa3fbcacbc880c7c9e603b3087a17906db95d14a35034889a253f23418.json)6
-rw-r--r--.sqlx/query-24bc0257eff3357322481e1314f70d13e8b0ca22b7652f1063ec7796cf307269.json (renamed from .sqlx/query-400de8e1dca27c48362b060c5b16e9ae908c4ea30e2d682d59d4720699b8de91.json)6
-rw-r--r--.sqlx/query-2e0aa3126267465ee1ae01e6856eff74a544f0a1c3692766e48a3182df5ada98.json (renamed from .sqlx/query-34d3bc2b18bc0813cbaafc1ef99603f820d3b391f212c2c0883506a53ee757ca.json)6
-rw-r--r--.sqlx/query-40e0310af2814435cca882701b86eddb5a1114202ba385f66f131b1601feec11.json (renamed from .sqlx/query-e702a0e7ff9a0a9808f8d45294ae908114b03719dc0cb237cec11f807bf757b1.json)6
-rw-r--r--.sqlx/query-4392eb4ec7257676acdce461b3bd7892ca01c2e3a2e0b1abfd8d7a57cbbf265e.json (renamed from .sqlx/query-ae9d1c997891f7e917cde554a251e76e93d3a43d1447faa4ec4085f7b3f60404.json)6
-rw-r--r--.sqlx/query-48884b1c153cbf6e10eed72e34ec512dfc9109b338eea475c209e5a005ae20c6.json (renamed from .sqlx/query-152cbd56cdce2ee7e94ae2568c112d0f6fe2aa136f0758a68f1eb66787db52f0.json)6
-rw-r--r--.sqlx/query-78d24fa907f3dcc0c129880e83b4ef41bad03b57937a27f98aebbef5268ef5f5.json (renamed from .sqlx/query-8e816ccef8ecee937d88ff31ca611395405093dbbed738579d715b2d623921be.json)6
-rw-r--r--.sqlx/query-7f5bbd935941210ba2f25e65327d32a68f41258a27249027f9200b2bbba047cb.json (renamed from .sqlx/query-647c3387fd01747b6318b946d2ea19a3007558abe81a1635c64ef7960acd30b2.json)6
-rw-r--r--.sqlx/query-91df35ca0c610b05fb353eeb7b7ef2f4fca0cd1c43ef45f32c9ce069d37fe659.json (renamed from .sqlx/query-b09438f4b1247a4e3991751de1fa77f2a18537d30e61ccbdcc947d0dba2b3da3.json)6
-rw-r--r--.sqlx/query-be33d1fb2f71093ed73efd90c8d4dfe599c70b36607a5dc436f28ba5b2ea9b2e.json (renamed from .sqlx/query-b991b34b491306780a1b6efa157b6ee50f32e1136ad9cbd91caa0add2ab3cdaa.json)6
-rw-r--r--.sqlx/query-d693a55bf9394ea79a892c3a5ed7d651ce7c5b3c7e8960458af03f1b533e3b1f.json (renamed from .sqlx/query-3fbec32aeb32c49e088f246c00151035dcf174cec137326b63e0cb0e4ae5cb60.json)6
-rw-r--r--.sqlx/query-db77e97937167d1edbbe88ebb2c0efd1c5718e721fe906765c28d040e446acd7.json (renamed from .sqlx/query-9da5b746de5d481eeb0755283b4094fa9dfa65dc95991f689355889606ab1c46.json)6
-rw-r--r--.sqlx/query-dbbc785bc45173db773f9179ae0758568f732d837c923cfe1b142181fb5d83f3.json (renamed from .sqlx/query-4224d5c1c4009e0d31b96bc7b1d9f6a2215c7c135720c1222170a1f6692c3a8a.json)6
-rw-r--r--.sqlx/query-dd613246fc1e87039f0a12ca8e2fa7c9cee66d2dfc3e516064982609cdcb3ff6.json (renamed from .sqlx/query-e2686f26f8646b4cd31beeb2060b9a6d6e0bbcb4cf8d01c48b297e6f0a950ebc.json)6
-rw-r--r--.sqlx/query-fce8f4fbd59a8b3b8531e10599914331682d58ace57214bfa26ccaa089592a24.json (renamed from .sqlx/query-bddc3b0d75f6048c36630db3abb8945a49ce18fb715d249bc9d93fc7d10e817d.json)6
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--docs/api/channels-messages.md4
-rw-r--r--docs/api/initial-setup.md4
-rw-r--r--docs/api/invitations.md4
-rw-r--r--src/channel/app.rs8
-rw-r--r--src/channel/mod.rs5
-rw-r--r--src/channel/name.rs30
-rw-r--r--src/channel/repo.rs14
-rw-r--r--src/channel/routes/channel/post.rs4
-rw-r--r--src/channel/routes/post.rs4
-rw-r--r--src/channel/snapshot.rs4
-rw-r--r--src/invite/app.rs8
-rw-r--r--src/invite/routes/invite/post.rs4
-rw-r--r--src/lib.rs1
-rw-r--r--src/login/app.rs4
-rw-r--r--src/login/mod.rs4
-rw-r--r--src/login/name.rs28
-rw-r--r--src/login/password.rs7
-rw-r--r--src/login/repo.rs10
-rw-r--r--src/login/routes/login/post.rs4
-rw-r--r--src/login/snapshot.rs4
-rw-r--r--src/message/app.rs4
-rw-r--r--src/message/body.rs30
-rw-r--r--src/message/mod.rs5
-rw-r--r--src/message/repo.rs28
-rw-r--r--src/message/snapshot.rs4
-rw-r--r--src/nfc.rs103
-rw-r--r--src/setup/app.rs4
-rw-r--r--src/setup/routes/post.rs4
-rw-r--r--src/test/fixtures/channel.rs10
-rw-r--r--src/test/fixtures/login.rs12
-rw-r--r--src/test/fixtures/message.rs6
-rw-r--r--src/token/app.rs4
-rw-r--r--src/token/repo/auth.rs6
-rw-r--r--src/token/repo/token.rs4
52 files changed, 346 insertions, 131 deletions
diff --git a/.sqlx/query-8d9b83fb53e4191ee58b68e65e101d0941f2a266679209f793fd27466b9e3719.json b/.sqlx/query-0caf898537dd38eea00322a9eb3578281dc70941faf05d53f8525034446ae52f.json
index 4d28006..8990436 100644
--- a/.sqlx/query-8d9b83fb53e4191ee58b68e65e101d0941f2a266679209f793fd27466b9e3719.json
+++ b/.sqlx/query-0caf898537dd38eea00322a9eb3578281dc70941faf05d53f8525034446ae52f.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n channel.name,\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where coalesce(channel.created_sequence <= $1, true)\n order by channel.name\n ",
+ "query": "\n select\n id as \"id: Id\",\n channel.name as \"name: Name\",\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where coalesce(channel.created_sequence <= $1, true)\n order by channel.name\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -46,5 +46,5 @@
true
]
},
- "hash": "8d9b83fb53e4191ee58b68e65e101d0941f2a266679209f793fd27466b9e3719"
+ "hash": "0caf898537dd38eea00322a9eb3578281dc70941faf05d53f8525034446ae52f"
}
diff --git a/.sqlx/query-836a37fa3fbcacbc880c7c9e603b3087a17906db95d14a35034889a253f23418.json b/.sqlx/query-0f0e4a6ac32b39f3bd7f4832389259b91bbffa182e32b224635031eead2fa82d.json
index 4770b7e..fd5a165 100644
--- a/.sqlx/query-836a37fa3fbcacbc880c7c9e603b3087a17906db95d14a35034889a253f23418.json
+++ b/.sqlx/query-0f0e4a6ac32b39f3bd7f4832389259b91bbffa182e32b224635031eead2fa82d.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n\t\t\t\tinsert into message\n\t\t\t\t\t(id, channel, sender, sent_at, sent_sequence, body)\n\t\t\t\tvalues ($1, $2, $3, $4, $5, $6)\n\t\t\t\treturning\n\t\t\t\t\tid as \"id: Id\",\n channel as \"channel: channel::Id\",\n sender as \"sender: login::Id\",\n sent_at as \"sent_at: DateTime\",\n sent_sequence as \"sent_sequence: Sequence\",\n\t\t\t\t\tbody\n\t\t\t",
+ "query": "\n insert into message\n (id, channel, sender, sent_at, sent_sequence, body)\n values ($1, $2, $3, $4, $5, $6)\n returning\n id as \"id: Id\",\n channel as \"channel: channel::Id\",\n sender as \"sender: login::Id\",\n sent_at as \"sent_at: DateTime\",\n sent_sequence as \"sent_sequence: Sequence\",\n body as \"body: Body\"\n ",
"describe": {
"columns": [
{
@@ -29,7 +29,7 @@
"type_info": "Integer"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 5,
"type_info": "Text"
}
@@ -46,5 +46,5 @@
true
]
},
- "hash": "836a37fa3fbcacbc880c7c9e603b3087a17906db95d14a35034889a253f23418"
+ "hash": "0f0e4a6ac32b39f3bd7f4832389259b91bbffa182e32b224635031eead2fa82d"
}
diff --git a/.sqlx/query-400de8e1dca27c48362b060c5b16e9ae908c4ea30e2d682d59d4720699b8de91.json b/.sqlx/query-24bc0257eff3357322481e1314f70d13e8b0ca22b7652f1063ec7796cf307269.json
index 0caa950..9f09a28 100644
--- a/.sqlx/query-400de8e1dca27c48362b060c5b16e9ae908c4ea30e2d682d59d4720699b8de91.json
+++ b/.sqlx/query-24bc0257eff3357322481e1314f70d13e8b0ca22b7652f1063ec7796cf307269.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n message.body,\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where coalesce(message.sent_sequence > $1, true)\n ",
+ "query": "\n select\n id as \"id: Id\",\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n message.body as \"body: Body\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where coalesce(message.sent_sequence > $1, true)\n ",
"describe": {
"columns": [
{
@@ -29,7 +29,7 @@
"type_info": "Integer"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 5,
"type_info": "Text"
},
@@ -58,5 +58,5 @@
true
]
},
- "hash": "400de8e1dca27c48362b060c5b16e9ae908c4ea30e2d682d59d4720699b8de91"
+ "hash": "24bc0257eff3357322481e1314f70d13e8b0ca22b7652f1063ec7796cf307269"
}
diff --git a/.sqlx/query-34d3bc2b18bc0813cbaafc1ef99603f820d3b391f212c2c0883506a53ee757ca.json b/.sqlx/query-2e0aa3126267465ee1ae01e6856eff74a544f0a1c3692766e48a3182df5ada98.json
index 63ff012..227d242 100644
--- a/.sqlx/query-34d3bc2b18bc0813cbaafc1ef99603f820d3b391f212c2c0883506a53ee757ca.json
+++ b/.sqlx/query-2e0aa3126267465ee1ae01e6856eff74a544f0a1c3692766e48a3182df5ada98.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n message.body,\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where message.sent_at < $1\n and deleted.id is null\n ",
+ "query": "\n select\n id as \"id: Id\",\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n message.body as \"body: Body\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where message.sent_at < $1\n and deleted.id is null\n ",
"describe": {
"columns": [
{
@@ -29,7 +29,7 @@
"type_info": "Integer"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 5,
"type_info": "Text"
},
@@ -58,5 +58,5 @@
false
]
},
- "hash": "34d3bc2b18bc0813cbaafc1ef99603f820d3b391f212c2c0883506a53ee757ca"
+ "hash": "2e0aa3126267465ee1ae01e6856eff74a544f0a1c3692766e48a3182df5ada98"
}
diff --git a/.sqlx/query-e702a0e7ff9a0a9808f8d45294ae908114b03719dc0cb237cec11f807bf757b1.json b/.sqlx/query-40e0310af2814435cca882701b86eddb5a1114202ba385f66f131b1601feec11.json
index ba35bb9..3147d7f 100644
--- a/.sqlx/query-e702a0e7ff9a0a9808f8d45294ae908114b03719dc0cb237cec11f807bf757b1.json
+++ b/.sqlx/query-40e0310af2814435cca882701b86eddb5a1114202ba385f66f131b1601feec11.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body,\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where message.channel = $1\n and deleted.id is null\n ",
+ "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body as \"body: Body\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where message.channel = $1\n and deleted.id is null\n ",
"describe": {
"columns": [
{
@@ -19,7 +19,7 @@
"type_info": "Text"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 3,
"type_info": "Text"
},
@@ -58,5 +58,5 @@
true
]
},
- "hash": "e702a0e7ff9a0a9808f8d45294ae908114b03719dc0cb237cec11f807bf757b1"
+ "hash": "40e0310af2814435cca882701b86eddb5a1114202ba385f66f131b1601feec11"
}
diff --git a/.sqlx/query-ae9d1c997891f7e917cde554a251e76e93d3a43d1447faa4ec4085f7b3f60404.json b/.sqlx/query-4392eb4ec7257676acdce461b3bd7892ca01c2e3a2e0b1abfd8d7a57cbbf265e.json
index cb345dc..1dd685b 100644
--- a/.sqlx/query-ae9d1c997891f7e917cde554a251e76e93d3a43d1447faa4ec4085f7b3f60404.json
+++ b/.sqlx/query-4392eb4ec7257676acdce461b3bd7892ca01c2e3a2e0b1abfd8d7a57cbbf265e.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n token.id as \"token_id: Id\",\n login.id as \"login_id: login::Id\",\n login.name as \"login_name\"\n from login\n join token on login.id = token.login\n where token.secret = $1\n ",
+ "query": "\n select\n token.id as \"token_id: Id\",\n login.id as \"login_id: login::Id\",\n login.name as \"login_name: Name\"\n from login\n join token on login.id = token.login\n where token.secret = $1\n ",
"describe": {
"columns": [
{
@@ -14,7 +14,7 @@
"type_info": "Text"
},
{
- "name": "login_name",
+ "name": "login_name: Name",
"ordinal": 2,
"type_info": "Text"
}
@@ -28,5 +28,5 @@
false
]
},
- "hash": "ae9d1c997891f7e917cde554a251e76e93d3a43d1447faa4ec4085f7b3f60404"
+ "hash": "4392eb4ec7257676acdce461b3bd7892ca01c2e3a2e0b1abfd8d7a57cbbf265e"
}
diff --git a/.sqlx/query-152cbd56cdce2ee7e94ae2568c112d0f6fe2aa136f0758a68f1eb66787db52f0.json b/.sqlx/query-48884b1c153cbf6e10eed72e34ec512dfc9109b338eea475c209e5a005ae20c6.json
index 2c7666f..b8d7303 100644
--- a/.sqlx/query-152cbd56cdce2ee7e94ae2568c112d0f6fe2aa136f0758a68f1eb66787db52f0.json
+++ b/.sqlx/query-48884b1c153cbf6e10eed72e34ec512dfc9109b338eea475c209e5a005ae20c6.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n channel.name,\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where id = $1\n ",
+ "query": "\n select\n id as \"id: Id\",\n channel.name as \"name: Name\",\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where id = $1\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -46,5 +46,5 @@
false
]
},
- "hash": "152cbd56cdce2ee7e94ae2568c112d0f6fe2aa136f0758a68f1eb66787db52f0"
+ "hash": "48884b1c153cbf6e10eed72e34ec512dfc9109b338eea475c209e5a005ae20c6"
}
diff --git a/.sqlx/query-8e816ccef8ecee937d88ff31ca611395405093dbbed738579d715b2d623921be.json b/.sqlx/query-78d24fa907f3dcc0c129880e83b4ef41bad03b57937a27f98aebbef5268ef5f5.json
index 5a038ac..09440ca 100644
--- a/.sqlx/query-8e816ccef8ecee937d88ff31ca611395405093dbbed738579d715b2d623921be.json
+++ b/.sqlx/query-78d24fa907f3dcc0c129880e83b4ef41bad03b57937a27f98aebbef5268ef5f5.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body,\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where id = $1\n ",
+ "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body as \"body: Body\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where id = $1\n ",
"describe": {
"columns": [
{
@@ -19,7 +19,7 @@
"type_info": "Text"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 3,
"type_info": "Text"
},
@@ -58,5 +58,5 @@
false
]
},
- "hash": "8e816ccef8ecee937d88ff31ca611395405093dbbed738579d715b2d623921be"
+ "hash": "78d24fa907f3dcc0c129880e83b4ef41bad03b57937a27f98aebbef5268ef5f5"
}
diff --git a/.sqlx/query-647c3387fd01747b6318b946d2ea19a3007558abe81a1635c64ef7960acd30b2.json b/.sqlx/query-7f5bbd935941210ba2f25e65327d32a68f41258a27249027f9200b2bbba047cb.json
index f706f81..83e730d 100644
--- a/.sqlx/query-647c3387fd01747b6318b946d2ea19a3007558abe81a1635c64ef7960acd30b2.json
+++ b/.sqlx/query-7f5bbd935941210ba2f25e65327d32a68f41258a27249027f9200b2bbba047cb.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n channel.id as \"id: Id\",\n channel.name,\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n left join message\n where channel.created_at < $1\n and message.id is null\n and deleted.id is null\n ",
+ "query": "\n select\n channel.id as \"id: Id\",\n channel.name as \"name: Name\",\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at?: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence?: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n left join message\n where channel.created_at < $1\n and message.id is null\n and deleted.id is null\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -46,5 +46,5 @@
false
]
},
- "hash": "647c3387fd01747b6318b946d2ea19a3007558abe81a1635c64ef7960acd30b2"
+ "hash": "7f5bbd935941210ba2f25e65327d32a68f41258a27249027f9200b2bbba047cb"
}
diff --git a/.sqlx/query-b09438f4b1247a4e3991751de1fa77f2a18537d30e61ccbdcc947d0dba2b3da3.json b/.sqlx/query-91df35ca0c610b05fb353eeb7b7ef2f4fca0cd1c43ef45f32c9ce069d37fe659.json
index 7c83aa1..6d2cb52 100644
--- a/.sqlx/query-b09438f4b1247a4e3991751de1fa77f2a18537d30e61ccbdcc947d0dba2b3da3.json
+++ b/.sqlx/query-91df35ca0c610b05fb353eeb7b7ef2f4fca0cd1c43ef45f32c9ce069d37fe659.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n name,\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n from login\n where coalesce(created_sequence <= $1, true)\n order by created_sequence\n ",
+ "query": "\n select\n id as \"id: Id\",\n name as \"name: Name\",\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n from login\n where coalesce(created_sequence <= $1, true)\n order by created_sequence\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -34,5 +34,5 @@
false
]
},
- "hash": "b09438f4b1247a4e3991751de1fa77f2a18537d30e61ccbdcc947d0dba2b3da3"
+ "hash": "91df35ca0c610b05fb353eeb7b7ef2f4fca0cd1c43ef45f32c9ce069d37fe659"
}
diff --git a/.sqlx/query-b991b34b491306780a1b6efa157b6ee50f32e1136ad9cbd91caa0add2ab3cdaa.json b/.sqlx/query-be33d1fb2f71093ed73efd90c8d4dfe599c70b36607a5dc436f28ba5b2ea9b2e.json
index 3901207..4b69943 100644
--- a/.sqlx/query-b991b34b491306780a1b6efa157b6ee50f32e1136ad9cbd91caa0add2ab3cdaa.json
+++ b/.sqlx/query-be33d1fb2f71093ed73efd90c8d4dfe599c70b36607a5dc436f28ba5b2ea9b2e.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n\t\t\t\tselect\n\t\t\t\t\tid as \"id: login::Id\",\n\t\t\t\t\tname,\n\t\t\t\t\tpassword_hash as \"password_hash: StoredHash\",\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n\t\t\t\tfrom login\n\t\t\t\twhere name = $1\n\t\t\t",
+ "query": "\n\t\t\t\tselect\n\t\t\t\t\tid as \"id: login::Id\",\n\t\t\t\t\tname as \"name: Name\",\n\t\t\t\t\tpassword_hash as \"password_hash: StoredHash\",\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n\t\t\t\tfrom login\n\t\t\t\twhere name = $1\n\t\t\t",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -40,5 +40,5 @@
false
]
},
- "hash": "b991b34b491306780a1b6efa157b6ee50f32e1136ad9cbd91caa0add2ab3cdaa"
+ "hash": "be33d1fb2f71093ed73efd90c8d4dfe599c70b36607a5dc436f28ba5b2ea9b2e"
}
diff --git a/.sqlx/query-3fbec32aeb32c49e088f246c00151035dcf174cec137326b63e0cb0e4ae5cb60.json b/.sqlx/query-d693a55bf9394ea79a892c3a5ed7d651ce7c5b3c7e8960458af03f1b533e3b1f.json
index 01e72b2..4291236 100644
--- a/.sqlx/query-3fbec32aeb32c49e088f246c00151035dcf174cec137326b63e0cb0e4ae5cb60.json
+++ b/.sqlx/query-d693a55bf9394ea79a892c3a5ed7d651ce7c5b3c7e8960458af03f1b533e3b1f.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n insert\n into login (id, name, password_hash, created_sequence, created_at)\n values ($1, $2, $3, $4, $5)\n returning\n id as \"id: Id\",\n name,\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n ",
+ "query": "\n insert\n into login (id, name, password_hash, created_sequence, created_at)\n values ($1, $2, $3, $4, $5)\n returning\n id as \"id: Id\",\n name as \"name: Name\",\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -34,5 +34,5 @@
false
]
},
- "hash": "3fbec32aeb32c49e088f246c00151035dcf174cec137326b63e0cb0e4ae5cb60"
+ "hash": "d693a55bf9394ea79a892c3a5ed7d651ce7c5b3c7e8960458af03f1b533e3b1f"
}
diff --git a/.sqlx/query-9da5b746de5d481eeb0755283b4094fa9dfa65dc95991f689355889606ab1c46.json b/.sqlx/query-db77e97937167d1edbbe88ebb2c0efd1c5718e721fe906765c28d040e446acd7.json
index 56a8498..0f2045f 100644
--- a/.sqlx/query-9da5b746de5d481eeb0755283b4094fa9dfa65dc95991f689355889606ab1c46.json
+++ b/.sqlx/query-db77e97937167d1edbbe88ebb2c0efd1c5718e721fe906765c28d040e446acd7.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n insert\n into channel (id, name, created_at, created_sequence)\n values ($1, $2, $3, $4)\n returning\n id as \"id: Id\",\n name as \"name!\", -- known non-null as we just set it\n created_at as \"created_at: DateTime\",\n created_sequence as \"created_sequence: Sequence\"\n ",
+ "query": "\n insert\n into channel (id, name, created_at, created_sequence)\n values ($1, $2, $3, $4)\n returning\n id as \"id: Id\",\n name as \"name!: Name\", -- known non-null as we just set it\n created_at as \"created_at: DateTime\",\n created_sequence as \"created_sequence: Sequence\"\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name!",
+ "name": "name!: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -34,5 +34,5 @@
false
]
},
- "hash": "9da5b746de5d481eeb0755283b4094fa9dfa65dc95991f689355889606ab1c46"
+ "hash": "db77e97937167d1edbbe88ebb2c0efd1c5718e721fe906765c28d040e446acd7"
}
diff --git a/.sqlx/query-4224d5c1c4009e0d31b96bc7b1d9f6a2215c7c135720c1222170a1f6692c3a8a.json b/.sqlx/query-dbbc785bc45173db773f9179ae0758568f732d837c923cfe1b142181fb5d83f3.json
index 767c217..7b5ac51 100644
--- a/.sqlx/query-4224d5c1c4009e0d31b96bc7b1d9f6a2215c7c135720c1222170a1f6692c3a8a.json
+++ b/.sqlx/query-dbbc785bc45173db773f9179ae0758568f732d837c923cfe1b142181fb5d83f3.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n name,\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n from login\n where coalesce(login.created_sequence > $1, true)\n ",
+ "query": "\n select\n id as \"id: Id\",\n name as \"name: Name\",\n created_sequence as \"created_sequence: Sequence\",\n created_at as \"created_at: DateTime\"\n from login\n where coalesce(login.created_sequence > $1, true)\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -34,5 +34,5 @@
false
]
},
- "hash": "4224d5c1c4009e0d31b96bc7b1d9f6a2215c7c135720c1222170a1f6692c3a8a"
+ "hash": "dbbc785bc45173db773f9179ae0758568f732d837c923cfe1b142181fb5d83f3"
}
diff --git a/.sqlx/query-e2686f26f8646b4cd31beeb2060b9a6d6e0bbcb4cf8d01c48b297e6f0a950ebc.json b/.sqlx/query-dd613246fc1e87039f0a12ca8e2fa7c9cee66d2dfc3e516064982609cdcb3ff6.json
index 1883035..fe8be3f 100644
--- a/.sqlx/query-e2686f26f8646b4cd31beeb2060b9a6d6e0bbcb4cf8d01c48b297e6f0a950ebc.json
+++ b/.sqlx/query-dd613246fc1e87039f0a12ca8e2fa7c9cee66d2dfc3e516064982609cdcb3ff6.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n id as \"id: Id\",\n channel.name,\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where coalesce(channel.created_sequence > $1, true)\n ",
+ "query": "\n select\n id as \"id: Id\",\n channel.name as \"name: Name\",\n channel.created_at as \"created_at: DateTime\",\n channel.created_sequence as \"created_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from channel\n left join channel_deleted as deleted\n using (id)\n where coalesce(channel.created_sequence > $1, true)\n ",
"describe": {
"columns": [
{
@@ -9,7 +9,7 @@
"type_info": "Text"
},
{
- "name": "name",
+ "name": "name: Name",
"ordinal": 1,
"type_info": "Text"
},
@@ -46,5 +46,5 @@
true
]
},
- "hash": "e2686f26f8646b4cd31beeb2060b9a6d6e0bbcb4cf8d01c48b297e6f0a950ebc"
+ "hash": "dd613246fc1e87039f0a12ca8e2fa7c9cee66d2dfc3e516064982609cdcb3ff6"
}
diff --git a/.sqlx/query-bddc3b0d75f6048c36630db3abb8945a49ce18fb715d249bc9d93fc7d10e817d.json b/.sqlx/query-fce8f4fbd59a8b3b8531e10599914331682d58ace57214bfa26ccaa089592a24.json
index abc1851..7aab764 100644
--- a/.sqlx/query-bddc3b0d75f6048c36630db3abb8945a49ce18fb715d249bc9d93fc7d10e817d.json
+++ b/.sqlx/query-fce8f4fbd59a8b3b8531e10599914331682d58ace57214bfa26ccaa089592a24.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body,\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where coalesce(message.sent_sequence <= $2, true)\n order by message.sent_sequence\n ",
+ "query": "\n select\n message.channel as \"channel: channel::Id\",\n message.sender as \"sender: login::Id\",\n id as \"id: Id\",\n message.body as \"body: Body\",\n message.sent_at as \"sent_at: DateTime\",\n message.sent_sequence as \"sent_sequence: Sequence\",\n deleted.deleted_at as \"deleted_at: DateTime\",\n deleted.deleted_sequence as \"deleted_sequence: Sequence\"\n from message\n left join message_deleted as deleted\n using (id)\n where coalesce(message.sent_sequence <= $2, true)\n order by message.sent_sequence\n ",
"describe": {
"columns": [
{
@@ -19,7 +19,7 @@
"type_info": "Text"
},
{
- "name": "body",
+ "name": "body: Body",
"ordinal": 3,
"type_info": "Text"
},
@@ -58,5 +58,5 @@
true
]
},
- "hash": "bddc3b0d75f6048c36630db3abb8945a49ce18fb715d249bc9d93fc7d10e817d"
+ "hash": "fce8f4fbd59a8b3b8531e10599914331682d58ace57214bfa26ccaa089592a24"
}
diff --git a/Cargo.lock b/Cargo.lock
index 2c0556a..c4454e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -825,6 +825,7 @@ dependencies = [
"thiserror",
"tokio",
"tokio-stream",
+ "unicode-normalization",
"uuid",
]
diff --git a/Cargo.toml b/Cargo.toml
index a131601..fe8fdfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,6 +31,7 @@ sqlx = { version = "=0.8.2", features = ["chrono", "runtime-tokio", "sqlite"] }
thiserror = "1.0.64"
tokio = { version = "1.40.0", features = ["rt", "macros", "rt-multi-thread"] }
tokio-stream = { version = "0.1.16", features = ["sync"] }
+unicode-normalization = "0.1.24"
uuid = { version = "1.11.0", features = ["v4"] }
[dev-dependencies]
diff --git a/docs/api/channels-messages.md b/docs/api/channels-messages.md
index 1ff037d..a441f52 100644
--- a/docs/api/channels-messages.md
+++ b/docs/api/channels-messages.md
@@ -70,6 +70,8 @@ The response will have the following fields:
| `id` | string | A unique identifier for the channel. This can be used to associate the channel with events, or to make API calls targeting the channel. |
| `name` | string | The channel's name. |
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
When completed, the service will emit a [channel created](events.md#channel-created) event with the channel's ID.
### Duplicate channel name
@@ -125,6 +127,8 @@ The response will have the following fields:
| `id` | string | A unique identifier for the message. This can be used to associate the message with events, or to make API calls targeting the message. |
| `body` | string | The message's body. |
+The returned message body may not be identical to the body as sent, as the body will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned body will include this normalization; the service will use the normalized body elsewhere, and does not store the originally submitted body.
+
When completed, the service will emit a [message sent](events.md#message-sent) event with the message's ID.
### Invalid channel ID
diff --git a/docs/api/initial-setup.md b/docs/api/initial-setup.md
index 3c5a8a6..306d798 100644
--- a/docs/api/initial-setup.md
+++ b/docs/api/initial-setup.md
@@ -71,6 +71,10 @@ The response will include the following fields:
| `id` | string | A unique identifier for the newly-created login. This can be used to associate the login with other events, or to make API calls targeting the login. |
| `name` | string | The login's name. |
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
+The provided password will also be converted to normalization form C. However, the normalized password is not returned to the client.
+
The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the initial login created for this request. See the [authentication](./authentication) section for details on how this cookie may be used.
The cookie will expire if it is not used regularly.
diff --git a/docs/api/invitations.md b/docs/api/invitations.md
index d3431d7..ddbef8a 100644
--- a/docs/api/invitations.md
+++ b/docs/api/invitations.md
@@ -150,6 +150,10 @@ The response will include the following fields:
| `id` | string | A unique identifier for the newly-created login. This can be used to associate the login with other events, or to make API calls targeting the login. |
| `name` | string | The login's name. |
+The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name.
+
+The provided password will also be converted to normalization form C. However, the normalized password is not returned to the client.
+
The response will include a `Set-Cookie` header for the `identity` cookie, providing the client with a newly-minted identity token associated with the login created for this request. See the [authentication](./authentication.md) section for details on how this cookie may be used.
The cookie will expire if it is not used regularly.
diff --git a/src/channel/app.rs b/src/channel/app.rs
index 75c662d..ea60943 100644
--- a/src/channel/app.rs
+++ b/src/channel/app.rs
@@ -2,7 +2,7 @@ use chrono::TimeDelta;
use itertools::Itertools;
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Channel, History, Id};
+use super::{repo::Provider as _, Channel, History, Id, Name};
use crate::{
clock::DateTime,
db::{Duplicate as _, NotFound as _},
@@ -20,14 +20,14 @@ impl<'a> Channels<'a> {
Self { db, events }
}
- pub async fn create(&self, name: &str, created_at: &DateTime) -> Result<Channel, CreateError> {
+ pub async fn create(&self, name: &Name, created_at: &DateTime) -> Result<Channel, CreateError> {
let mut tx = self.db.begin().await?;
let created = tx.sequence().next(created_at).await?;
let channel = tx
.channels()
.create(name, &created)
.await
- .duplicate(|| CreateError::DuplicateName(name.into()))?;
+ .duplicate(|| CreateError::DuplicateName(name.clone()))?;
tx.commit().await?;
self.events
@@ -134,7 +134,7 @@ impl<'a> Channels<'a> {
#[derive(Debug, thiserror::Error)]
pub enum CreateError {
#[error("channel named {0} already exists")]
- DuplicateName(String),
+ DuplicateName(Name),
#[error(transparent)]
Database(#[from] sqlx::Error),
}
diff --git a/src/channel/mod.rs b/src/channel/mod.rs
index eb8200b..fb13e92 100644
--- a/src/channel/mod.rs
+++ b/src/channel/mod.rs
@@ -2,8 +2,11 @@ pub mod app;
pub mod event;
mod history;
mod id;
+mod name;
pub mod repo;
mod routes;
mod snapshot;
-pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Channel};
+pub use self::{
+ event::Event, history::History, id::Id, name::Name, routes::router, snapshot::Channel,
+};
diff --git a/src/channel/name.rs b/src/channel/name.rs
new file mode 100644
index 0000000..fc82dec
--- /dev/null
+++ b/src/channel/name.rs
@@ -0,0 +1,30 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(
+ Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type,
+)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Name(nfc::String);
+
+impl fmt::Display for Name {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(name) = self;
+ name.fmt(f)
+ }
+}
+
+impl From<String> for Name {
+ fn from(name: String) -> Self {
+ Self(name.into())
+ }
+}
+
+impl From<Name> for String {
+ fn from(name: Name) -> Self {
+ let Name(name) = name;
+ name.into()
+ }
+}
diff --git a/src/channel/repo.rs b/src/channel/repo.rs
index 27d35f0..3353bfd 100644
--- a/src/channel/repo.rs
+++ b/src/channel/repo.rs
@@ -1,7 +1,7 @@
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
- channel::{Channel, History, Id},
+ channel::{Channel, History, Id, Name},
clock::DateTime,
event::{Instant, ResumePoint, Sequence},
};
@@ -19,7 +19,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> {
pub struct Channels<'t>(&'t mut SqliteConnection);
impl<'c> Channels<'c> {
- pub async fn create(&mut self, name: &str, created: &Instant) -> Result<History, sqlx::Error> {
+ pub async fn create(&mut self, name: &Name, created: &Instant) -> Result<History, sqlx::Error> {
let id = Id::generate();
let channel = sqlx::query!(
r#"
@@ -28,7 +28,7 @@ impl<'c> Channels<'c> {
values ($1, $2, $3, $4)
returning
id as "id: Id",
- name as "name!", -- known non-null as we just set it
+ name as "name!: Name", -- known non-null as we just set it
created_at as "created_at: DateTime",
created_sequence as "created_sequence: Sequence"
"#,
@@ -57,7 +57,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
@@ -89,7 +89,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -125,7 +125,7 @@ impl<'c> Channels<'c> {
r#"
select
id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -235,7 +235,7 @@ impl<'c> Channels<'c> {
r#"
select
channel.id as "id: Id",
- channel.name,
+ channel.name as "name: Name",
channel.created_at as "created_at: DateTime",
channel.created_sequence as "created_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs
index b489a77..d0cae05 100644
--- a/src/channel/routes/channel/post.rs
+++ b/src/channel/routes/channel/post.rs
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
login::Login,
- message::{app::SendError, Message},
+ message::{app::SendError, Body, Message},
};
pub async fn handler(
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub body: String,
+ pub body: Body,
}
#[derive(Debug)]
diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs
index a05c312..d354f79 100644
--- a/src/channel/routes/post.rs
+++ b/src/channel/routes/post.rs
@@ -6,7 +6,7 @@ use axum::{
use crate::{
app::App,
- channel::{app, Channel},
+ channel::{app, Channel, Name},
clock::RequestedAt,
error::Internal,
login::Login,
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
}
#[derive(Debug)]
diff --git a/src/channel/snapshot.rs b/src/channel/snapshot.rs
index 2b7d89a..dc2894d 100644
--- a/src/channel/snapshot.rs
+++ b/src/channel/snapshot.rs
@@ -1,13 +1,13 @@
use super::{
event::{Created, Event},
- Id,
+ Id, Name,
};
use crate::clock::DateTime;
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct Channel {
pub id: Id,
- pub name: String,
+ pub name: Name,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTime>,
}
diff --git a/src/invite/app.rs b/src/invite/app.rs
index ee7f74f..285a819 100644
--- a/src/invite/app.rs
+++ b/src/invite/app.rs
@@ -6,7 +6,7 @@ use crate::{
clock::DateTime,
db::{Duplicate as _, NotFound as _},
event::repo::Provider as _,
- login::{repo::Provider as _, Login, Password},
+ login::{repo::Provider as _, Login, Name, Password},
token::{repo::Provider as _, Secret},
};
@@ -42,7 +42,7 @@ impl<'a> Invites<'a> {
pub async fn accept(
&self,
invite: &Id,
- name: &str,
+ name: &Name,
password: &Password,
accepted_at: &DateTime,
) -> Result<(Login, Secret), AcceptError> {
@@ -68,7 +68,7 @@ impl<'a> Invites<'a> {
.logins()
.create(name, &password_hash, &created)
.await
- .duplicate(|| AcceptError::DuplicateLogin(name.into()))?;
+ .duplicate(|| AcceptError::DuplicateLogin(name.clone()))?;
let secret = tx.tokens().issue(&login, accepted_at).await?;
tx.commit().await?;
@@ -92,7 +92,7 @@ pub enum AcceptError {
#[error("invite not found: {0}")]
NotFound(Id),
#[error("name in use: {0}")]
- DuplicateLogin(String),
+ DuplicateLogin(Name),
#[error(transparent)]
Database(#[from] sqlx::Error),
#[error(transparent)]
diff --git a/src/invite/routes/invite/post.rs b/src/invite/routes/invite/post.rs
index c072929..8160465 100644
--- a/src/invite/routes/invite/post.rs
+++ b/src/invite/routes/invite/post.rs
@@ -9,7 +9,7 @@ use crate::{
clock::RequestedAt,
error::{Internal, NotFound},
invite::app,
- login::{Login, Password},
+ login::{Login, Name, Password},
token::extract::IdentityToken,
};
@@ -31,7 +31,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/lib.rs b/src/lib.rs
index 73a2cb0..4d0d9b9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,7 @@ mod id;
mod invite;
mod login;
mod message;
+mod nfc;
mod setup;
#[cfg(test)]
mod test;
diff --git a/src/login/app.rs b/src/login/app.rs
index b6f7e1c..ebc1c00 100644
--- a/src/login/app.rs
+++ b/src/login/app.rs
@@ -1,6 +1,6 @@
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Login, Password};
+use super::{repo::Provider as _, Login, Name, Password};
use crate::{
clock::DateTime,
event::{repo::Provider as _, Broadcaster, Event},
@@ -18,7 +18,7 @@ impl<'a> Logins<'a> {
pub async fn create(
&self,
- name: &str,
+ name: &Name,
password: &Password,
created_at: &DateTime,
) -> Result<Login, CreateError> {
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 98cc3d7..71d5bfc 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -4,11 +4,13 @@ pub mod event;
pub mod extract;
mod history;
mod id;
+mod name;
pub mod password;
pub mod repo;
mod routes;
mod snapshot;
pub use self::{
- event::Event, history::History, id::Id, password::Password, routes::router, snapshot::Login,
+ event::Event, history::History, id::Id, name::Name, password::Password, routes::router,
+ snapshot::Login,
};
diff --git a/src/login/name.rs b/src/login/name.rs
new file mode 100644
index 0000000..d882ff9
--- /dev/null
+++ b/src/login/name.rs
@@ -0,0 +1,28 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Name(nfc::String);
+
+impl fmt::Display for Name {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(name) = self;
+ name.fmt(f)
+ }
+}
+
+impl From<String> for Name {
+ fn from(name: String) -> Self {
+ Self(name.into())
+ }
+}
+
+impl From<Name> for String {
+ fn from(name: Name) -> Self {
+ let Name(name) = name;
+ name.into()
+ }
+}
diff --git a/src/login/password.rs b/src/login/password.rs
index 14fd981..f9ecf37 100644
--- a/src/login/password.rs
+++ b/src/login/password.rs
@@ -4,6 +4,8 @@ use argon2::Argon2;
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use rand_core::OsRng;
+use crate::nfc;
+
#[derive(sqlx::Type)]
#[sqlx(transparent)]
pub struct StoredHash(String);
@@ -31,7 +33,7 @@ impl fmt::Debug for StoredHash {
#[derive(serde::Deserialize)]
#[serde(transparent)]
-pub struct Password(String);
+pub struct Password(nfc::String);
impl Password {
pub fn hash(&self) -> Result<StoredHash, password_hash::Error> {
@@ -56,9 +58,8 @@ impl fmt::Debug for Password {
}
}
-#[cfg(test)]
impl From<String> for Password {
fn from(password: String) -> Self {
- Self(password)
+ Password(password.into())
}
}
diff --git a/src/login/repo.rs b/src/login/repo.rs
index 7d0fcb1..204329f 100644
--- a/src/login/repo.rs
+++ b/src/login/repo.rs
@@ -3,7 +3,7 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
clock::DateTime,
event::{Instant, ResumePoint, Sequence},
- login::{password::StoredHash, History, Id, Login},
+ login::{password::StoredHash, History, Id, Login, Name},
};
pub trait Provider {
@@ -21,7 +21,7 @@ pub struct Logins<'t>(&'t mut SqliteConnection);
impl<'c> Logins<'c> {
pub async fn create(
&mut self,
- name: &str,
+ name: &Name,
password_hash: &StoredHash,
created: &Instant,
) -> Result<History, sqlx::Error> {
@@ -34,7 +34,7 @@ impl<'c> Logins<'c> {
values ($1, $2, $3, $4, $5)
returning
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
"#,
@@ -62,7 +62,7 @@ impl<'c> Logins<'c> {
r#"
select
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
from login
@@ -88,7 +88,7 @@ impl<'c> Logins<'c> {
r#"
select
id as "id: Id",
- name,
+ name as "name: Name",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
from login
diff --git a/src/login/routes/login/post.rs b/src/login/routes/login/post.rs
index 67eaa6d..7a685e2 100644
--- a/src/login/routes/login/post.rs
+++ b/src/login/routes/login/post.rs
@@ -8,7 +8,7 @@ use crate::{
app::App,
clock::RequestedAt,
error::Internal,
- login::{Login, Password},
+ login::{Login, Name, Password},
token::{app, extract::IdentityToken},
};
@@ -29,7 +29,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/login/snapshot.rs b/src/login/snapshot.rs
index 1a92f5c..85800e4 100644
--- a/src/login/snapshot.rs
+++ b/src/login/snapshot.rs
@@ -1,6 +1,6 @@
use super::{
event::{Created, Event},
- Id,
+ Id, Name,
};
// This also implements FromRequestParts (see `./extract.rs`). As a result, it
@@ -10,7 +10,7 @@ use super::{
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct Login {
pub id: Id,
- pub name: String,
+ pub name: Name,
// The omission of the hashed password is deliberate, to minimize the
// chance that it ends up tangled up in debug output or in some other chunk
// of logic elsewhere.
diff --git a/src/message/app.rs b/src/message/app.rs
index 4e50513..af87553 100644
--- a/src/message/app.rs
+++ b/src/message/app.rs
@@ -2,7 +2,7 @@ use chrono::TimeDelta;
use itertools::Itertools;
use sqlx::sqlite::SqlitePool;
-use super::{repo::Provider as _, Id, Message};
+use super::{repo::Provider as _, Body, Id, Message};
use crate::{
channel::{self, repo::Provider as _},
clock::DateTime,
@@ -26,7 +26,7 @@ impl<'a> Messages<'a> {
channel: &channel::Id,
sender: &Login,
sent_at: &DateTime,
- body: &str,
+ body: &Body,
) -> Result<Message, SendError> {
let mut tx = self.db.begin().await?;
let channel = tx
diff --git a/src/message/body.rs b/src/message/body.rs
new file mode 100644
index 0000000..a415f85
--- /dev/null
+++ b/src/message/body.rs
@@ -0,0 +1,30 @@
+use std::fmt;
+
+use crate::nfc;
+
+#[derive(
+ Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, sqlx::Type,
+)]
+#[serde(transparent)]
+#[sqlx(transparent)]
+pub struct Body(nfc::String);
+
+impl fmt::Display for Body {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(body) = self;
+ body.fmt(f)
+ }
+}
+
+impl From<String> for Body {
+ fn from(body: String) -> Self {
+ Self(body.into())
+ }
+}
+
+impl From<Body> for String {
+ fn from(body: Body) -> Self {
+ let Body(body) = body;
+ body.into()
+ }
+}
diff --git a/src/message/mod.rs b/src/message/mod.rs
index a8f51ab..c2687bc 100644
--- a/src/message/mod.rs
+++ b/src/message/mod.rs
@@ -1,4 +1,5 @@
pub mod app;
+mod body;
pub mod event;
mod history;
mod id;
@@ -6,4 +7,6 @@ pub mod repo;
mod routes;
mod snapshot;
-pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Message};
+pub use self::{
+ body::Body, event::Event, history::History, id::Id, routes::router, snapshot::Message,
+};
diff --git a/src/message/repo.rs b/src/message/repo.rs
index 85a69fc..4cfefec 100644
--- a/src/message/repo.rs
+++ b/src/message/repo.rs
@@ -1,6 +1,6 @@
use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
-use super::{snapshot::Message, History, Id};
+use super::{snapshot::Message, Body, History, Id};
use crate::{
channel,
clock::DateTime,
@@ -26,24 +26,24 @@ impl<'c> Messages<'c> {
channel: &channel::History,
sender: &Login,
sent: &Instant,
- body: &str,
+ body: &Body,
) -> Result<History, sqlx::Error> {
let id = Id::generate();
let channel_id = channel.id();
let message = sqlx::query!(
r#"
- insert into message
- (id, channel, sender, sent_at, sent_sequence, body)
- values ($1, $2, $3, $4, $5, $6)
- returning
- id as "id: Id",
+ insert into message
+ (id, channel, sender, sent_at, sent_sequence, body)
+ values ($1, $2, $3, $4, $5, $6)
+ returning
+ id as "id: Id",
channel as "channel: channel::Id",
sender as "sender: login::Id",
sent_at as "sent_at: DateTime",
sent_sequence as "sent_sequence: Sequence",
- body
- "#,
+ body as "body: Body"
+ "#,
id,
channel_id,
sender.id,
@@ -76,7 +76,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -113,7 +113,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at: DateTime",
@@ -150,7 +150,7 @@ impl<'c> Messages<'c> {
message.channel as "channel: channel::Id",
message.sender as "sender: login::Id",
id as "id: Id",
- message.body,
+ message.body as "body: Body",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
deleted.deleted_at as "deleted_at?: DateTime",
@@ -256,7 +256,7 @@ impl<'c> Messages<'c> {
message.sender as "sender: login::Id",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
- message.body,
+ message.body as "body: Body",
deleted.deleted_at as "deleted_at?: DateTime",
deleted.deleted_sequence as "deleted_sequence?: Sequence"
from message
@@ -293,7 +293,7 @@ impl<'c> Messages<'c> {
message.sender as "sender: login::Id",
message.sent_at as "sent_at: DateTime",
message.sent_sequence as "sent_sequence: Sequence",
- message.body,
+ message.body as "body: Body",
deleted.deleted_at as "deleted_at: DateTime",
deleted.deleted_sequence as "deleted_sequence: Sequence"
from message
diff --git a/src/message/snapshot.rs b/src/message/snapshot.rs
index 7300918..53b7176 100644
--- a/src/message/snapshot.rs
+++ b/src/message/snapshot.rs
@@ -1,6 +1,6 @@
use super::{
event::{Event, Sent},
- Id,
+ Body, Id,
};
use crate::{channel, clock::DateTime, event::Instant, login};
@@ -11,7 +11,7 @@ pub struct Message {
pub channel: channel::Id,
pub sender: login::Id,
pub id: Id,
- pub body: String,
+ pub body: Body,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTime>,
}
diff --git a/src/nfc.rs b/src/nfc.rs
new file mode 100644
index 0000000..70e936c
--- /dev/null
+++ b/src/nfc.rs
@@ -0,0 +1,103 @@
+use std::{fmt, string::String as StdString};
+
+use sqlx::{
+ encode::{Encode, IsNull},
+ Database, Decode, Type,
+};
+use unicode_normalization::UnicodeNormalization as _;
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
+#[serde(from = "StdString", into = "StdString")]
+pub struct String(StdString);
+
+impl fmt::Display for String {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let Self(value) = self;
+ value.fmt(f)
+ }
+}
+
+impl From<StdString> for String {
+ fn from(value: StdString) -> Self {
+ let value = value.nfc().collect();
+
+ Self(value)
+ }
+}
+
+impl From<String> for StdString {
+ fn from(value: String) -> Self {
+ let String(value) = value;
+ value
+ }
+}
+
+impl std::ops::Deref for String {
+ type Target = StdString;
+
+ fn deref(&self) -> &Self::Target {
+ let Self(value) = self;
+ value
+ }
+}
+
+// Type is manually implemented so that we can implement Decode to do
+// normalization on read. Implementation is otherwise based on
+// `#[derive(sqlx::Type)]` with the `#[sqlx(transparent)]` attribute.
+impl<DB> Type<DB> for String
+where
+ DB: Database,
+ StdString: Type<DB>,
+{
+ fn type_info() -> <DB as Database>::TypeInfo {
+ <StdString as Type<DB>>::type_info()
+ }
+
+ fn compatible(ty: &<DB as Database>::TypeInfo) -> bool {
+ <StdString as Type<DB>>::compatible(ty)
+ }
+}
+
+impl<'r, DB> Decode<'r, DB> for String
+where
+ DB: Database,
+ StdString: Decode<'r, DB>,
+{
+ fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
+ let value = StdString::decode(value)?;
+ let value = value.nfc().collect();
+ Ok(Self(value))
+ }
+}
+
+impl<'q, DB> Encode<'q, DB> for String
+where
+ DB: Database,
+ StdString: Encode<'q, DB>,
+{
+ fn encode_by_ref(
+ &self,
+ buf: &mut <DB as Database>::ArgumentBuffer<'q>,
+ ) -> Result<IsNull, sqlx::error::BoxDynError> {
+ let Self(value) = self;
+ value.encode_by_ref(buf)
+ }
+
+ fn encode(
+ self,
+ buf: &mut <DB as Database>::ArgumentBuffer<'q>,
+ ) -> Result<IsNull, sqlx::error::BoxDynError> {
+ let Self(value) = self;
+ value.encode(buf)
+ }
+
+ fn produces(&self) -> Option<<DB as Database>::TypeInfo> {
+ let Self(value) = self;
+ value.produces()
+ }
+
+ fn size_hint(&self) -> usize {
+ let Self(value) = self;
+ value.size_hint()
+ }
+}
diff --git a/src/setup/app.rs b/src/setup/app.rs
index d015813..9fbcf6d 100644
--- a/src/setup/app.rs
+++ b/src/setup/app.rs
@@ -4,7 +4,7 @@ use super::repo::Provider as _;
use crate::{
clock::DateTime,
event::{repo::Provider as _, Broadcaster, Event},
- login::{repo::Provider as _, Login, Password},
+ login::{repo::Provider as _, Login, Name, Password},
token::{repo::Provider as _, Secret},
};
@@ -20,7 +20,7 @@ impl<'a> Setup<'a> {
pub async fn initial(
&self,
- name: &str,
+ name: &Name,
password: &Password,
created_at: &DateTime,
) -> Result<(Login, Secret), Error> {
diff --git a/src/setup/routes/post.rs b/src/setup/routes/post.rs
index 34f4ed2..6a3fa11 100644
--- a/src/setup/routes/post.rs
+++ b/src/setup/routes/post.rs
@@ -8,7 +8,7 @@ use crate::{
app::App,
clock::RequestedAt,
error::Internal,
- login::{Login, Password},
+ login::{Login, Name, Password},
setup::app,
token::extract::IdentityToken,
};
@@ -30,7 +30,7 @@ pub async fn handler(
#[derive(serde::Deserialize)]
pub struct Request {
- pub name: String,
+ pub name: Name,
pub password: Password,
}
diff --git a/src/test/fixtures/channel.rs b/src/test/fixtures/channel.rs
index a1dda61..024ac1b 100644
--- a/src/test/fixtures/channel.rs
+++ b/src/test/fixtures/channel.rs
@@ -8,7 +8,7 @@ use rand;
use crate::{
app::App,
- channel::{self, Channel},
+ channel::{self, Channel, Name},
clock::RequestedAt,
event::Event,
};
@@ -21,13 +21,13 @@ pub async fn create(app: &App, created_at: &RequestedAt) -> Channel {
.expect("should always succeed if the channel is actually new")
}
-pub fn propose() -> String {
- rand::random::<Name>().to_string()
+pub fn propose() -> Name {
+ rand::random::<NameTemplate>().to_string().into()
}
-struct Name(String);
+struct NameTemplate(String);
faker_impl_from_templates! {
- Name; "{} {}", CityName, FullName;
+ NameTemplate; "{} {}", CityName, FullName;
}
pub fn events(event: Event) -> future::Ready<Option<channel::Event>> {
diff --git a/src/test/fixtures/login.rs b/src/test/fixtures/login.rs
index b6766fe..0a42320 100644
--- a/src/test/fixtures/login.rs
+++ b/src/test/fixtures/login.rs
@@ -4,7 +4,7 @@ use uuid::Uuid;
use crate::{
app::App,
clock::RequestedAt,
- login::{self, Login, Password},
+ login::{self, Login, Name, Password},
};
pub async fn create_with_password(app: &App, created_at: &RequestedAt) -> (Login, Password) {
@@ -29,16 +29,16 @@ pub async fn create(app: &App, created_at: &RequestedAt) -> Login {
pub fn fictitious() -> Login {
Login {
id: login::Id::generate(),
- name: name(),
+ name: propose_name(),
}
}
-pub fn propose() -> (String, Password) {
- (name(), propose_password())
+pub fn propose() -> (Name, Password) {
+ (propose_name(), propose_password())
}
-fn name() -> String {
- rand::random::<internet::Username>().to_string()
+fn propose_name() -> Name {
+ rand::random::<internet::Username>().to_string().into()
}
pub fn propose_password() -> Password {
diff --git a/src/test/fixtures/message.rs b/src/test/fixtures/message.rs
index eb00e7c..c450bce 100644
--- a/src/test/fixtures/message.rs
+++ b/src/test/fixtures/message.rs
@@ -8,7 +8,7 @@ use crate::{
clock::RequestedAt,
event::Event,
login::Login,
- message::{self, Message},
+ message::{self, Body, Message},
};
pub async fn send(app: &App, channel: &Channel, login: &Login, sent_at: &RequestedAt) -> Message {
@@ -20,8 +20,8 @@ pub async fn send(app: &App, channel: &Channel, login: &Login, sent_at: &Request
.expect("should succeed if the channel exists")
}
-pub fn propose() -> String {
- rand::random::<Paragraphs>().to_string()
+pub fn propose() -> Body {
+ rand::random::<Paragraphs>().to_string().into()
}
pub fn events(event: Event) -> future::Ready<Option<message::Event>> {
diff --git a/src/token/app.rs b/src/token/app.rs
index 0dc1a46..d4dd1a0 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -12,7 +12,7 @@ use super::{
use crate::{
clock::DateTime,
db::NotFound as _,
- login::{Login, Password},
+ login::{Login, Name, Password},
};
pub struct Tokens<'a> {
@@ -27,7 +27,7 @@ impl<'a> Tokens<'a> {
pub async fn login(
&self,
- name: &str,
+ name: &Name,
password: &Password,
login_at: &DateTime,
) -> Result<(Login, Secret), LoginError> {
diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs
index 88d0878..c621b65 100644
--- a/src/token/repo/auth.rs
+++ b/src/token/repo/auth.rs
@@ -3,7 +3,7 @@ use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction};
use crate::{
clock::DateTime,
event::{Instant, Sequence},
- login::{self, password::StoredHash, History, Login},
+ login::{self, password::StoredHash, History, Login, Name},
};
pub trait Provider {
@@ -19,12 +19,12 @@ impl<'c> Provider for Transaction<'c, Sqlite> {
pub struct Auth<'t>(&'t mut SqliteConnection);
impl<'t> Auth<'t> {
- pub async fn for_name(&mut self, name: &str) -> Result<(History, StoredHash), sqlx::Error> {
+ pub async fn for_name(&mut self, name: &Name) -> Result<(History, StoredHash), sqlx::Error> {
let found = sqlx::query!(
r#"
select
id as "id: login::Id",
- name,
+ name as "name: Name",
password_hash as "password_hash: StoredHash",
created_sequence as "created_sequence: Sequence",
created_at as "created_at: DateTime"
diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs
index c592dcd..960bb72 100644
--- a/src/token/repo/token.rs
+++ b/src/token/repo/token.rs
@@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::{
clock::DateTime,
- login::{self, History, Login},
+ login::{self, History, Login, Name},
token::{Id, Secret},
};
@@ -128,7 +128,7 @@ impl<'c> Tokens<'c> {
select
token.id as "token_id: Id",
login.id as "login_id: login::Id",
- login.name as "login_name"
+ login.name as "login_name: Name"
from login
join token on login.id = token.login
where token.secret = $1