diff options
| author | ojacobson <ojacobson@noreply.codeberg.org> | 2025-08-26 23:36:40 +0200 |
|---|---|---|
| committer | ojacobson <ojacobson@noreply.codeberg.org> | 2025-08-26 23:36:40 +0200 |
| commit | 7b131e35fdea1a68aaf9230d157bafb200557ef8 (patch) | |
| tree | b0f3ee3ac604947a8866c692a080d3f6064d7d03 /migrations/20250822213537_split_user.sql | |
| parent | 68f54c8904ec6ff2ac3be4c514fa4aa05a67cb68 (diff) | |
| parent | d0d5fa20200a7ad70173ba87ae47c33b60f44a3b (diff) | |
Split `user` into a chat-facing entity and an authentication-facing entity.
The taxonomy is now as follows:
* A _login_ is someone's identity for the purposes of authenticating to the service. Logins are not synchronized, and in fact are not published anywhere in the current API. They have a login ID, a name and a password.
* A _user_ is someone's identity for the purpose of participating in conversations. Users _are_ synchronized, as before. They have a user ID, a name, and a creation instant for the purposes of synchronization.
## API changes
* `GET /api/boot` method now returns a `login` key instead of a `user` key. The structure of the nested value is unchanged. This change is not backwards-compatible; the included client and the docs have been updated accordingly.
## Server implementation
* Most app methods that took a `&User` as an identity now take a `&Login` as an identity, instead. Where a `User` is needed, the new `tx.users().for_login(&login)` database access method resolves a `Login` to its corresponding `user::History`, which can then be turned into a `User` at whatever point in time is most appropriate.
This adds a few new error cases to methods that traverse the login-to-history-to-user chain. Those cases are presently unreachable, but I've fully fleshed them out so that they don't bite us later. Most of the resulting errors, however, are captured as internal server errors.
* There is a new `app.logins()` application entry point, dealing with login identities and password-based logins.
* `app.tokens()` is a bit more limited in scope to only things that work with an existing token.
That has the side effect of splitting up logging in (in `app.logins().with_password(…)`) and logging out (in `app.tokens().logout(…)`).
## Schema changes
The `user` table has been split:
* `login` holds the data needed for the user to log in - their login ID, their name, and their password.
* `user` now holds only the user ID and the event data for the user's `created` instant. Reconstructing a `User` struct requires joining in data from both `login` and `user`.
In theory, the relationship is one-way: every user has a login. In practice, it's reciprocal: every login has a user and every user has a login.
Relationships with downstream tables have been modified to suit:
* `message` still refers to `user` for authorship information.
* `invite` still refers to `user` for originator information.
* `token` refers to `login` for authentication information.
## Blimy, that's big
Yeah, I know. It's hard to avoid and I'm not sure the effort of making this in incremental steps is worth it.
Authentication logic has a way of getting into all sorts of corners, and Pilcrow is no different. In order for the new taxonomy to make sense, all of the places that previously used `User` as a representation of an authenticated identity have to be updated, and it's easier to do that all at once, so that we can retire all the code that _supports_ using a `User` that way.
Merges split-user into main.
Diffstat (limited to 'migrations/20250822213537_split_user.sql')
| -rw-r--r-- | migrations/20250822213537_split_user.sql | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/migrations/20250822213537_split_user.sql b/migrations/20250822213537_split_user.sql new file mode 100644 index 0000000..1dbbad6 --- /dev/null +++ b/migrations/20250822213537_split_user.sql @@ -0,0 +1,195 @@ +-- First, migrate the table we're actually interested in - `user`. This splits it into two parts: +-- +-- * login, which is concerned with authentication data (and is non-synchronized, +-- though at the DB that doesn't matter much), and +-- * user, which is concerned with chat data (and is synchronized, ditto). + +alter table user + rename to old_user; + +create table login ( + id text + not null + primary key, + display_name text + not null, + canonical_name text + not null + unique, + password text + not null +); + +insert into + login (id, display_name, canonical_name, password) +select + id, + display_name, + canonical_name, + password_hash as password +from + old_user; + +create table user ( + id text + not null + primary key + references login (id), + created_at text + not null, + created_sequence bigint + not null + unique +); + +insert into + user (id, created_at, created_sequence) +select + id, + created_at, + created_sequence +from + old_user; + +-- Now, recreate the entire rest of the owl. Everything that referenced the original `user` table _except tokens_ +-- references the user table. Tokens, which are authentication data, reference the login (authn) table. + +alter table token + rename to old_token; + +create table token ( + id text + not null + primary key, + secret text + not null + unique, + login text + not null + references login (id), + issued_at text + not null, + last_used_at text + not null +); + +insert into + token (id, secret, login, issued_at, last_used_at) +select + id, + secret, + user as login, + issued_at, + last_used_at +from + old_token; + +alter table invite + rename to old_invite; + +create table invite ( + id text + primary key + not null, + issuer text + not null + references user (id), + issued_at text + not null +); + +insert into + invite (id, issuer, issued_at) +select + id, + issuer, + issued_at +from + old_invite; + +alter table message + rename to old_message; + +create table message ( + id text + not null + primary key, + conversation text + not null + references conversation (id), + sender text + not null + references user (id), + body text + null, + sent_at text + not null, + sent_sequence bigint + unique + not null, + last_sequence bigint + not null +); + +insert into + message (id, conversation, sender, body, sent_at, sent_sequence, last_sequence) +select + id, + conversation, + sender, + body, + sent_at, + sent_sequence, + last_sequence +from + old_message; + +alter table message_deleted + rename to old_message_deleted; + +create table message_deleted ( + id text + not null + primary key + references message (id), + deleted_at text + not null, + deleted_sequence bigint + unique + not null +); + +insert into + message_deleted (id, deleted_at, deleted_sequence) +select + id, + deleted_at, + deleted_sequence +from + message_deleted; + +drop table old_message_deleted; +drop table old_message; +drop table old_invite; +drop table old_token; +drop table old_user; + +create index token_issued_at + on token (issued_at); +create index token_last_used_at + on token (last_used_at); +create index token_login + on token (login); + +create index invite_issued_at + on invite (issued_at); + +create index message_sent_at + on message (sent_at); +create index message_conversation + on message (conversation); +create index message_last_sequence + on message (last_sequence); + +create index message_deleted_deleted_at + on message_deleted (deleted_at); |
