From b2c87dd2076bd18e4ec146b3145b1d30591598dc Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Tue, 11 Oct 2022 20:06:29 -0700 Subject: [PATCH] account + local_account -> actor + account --- src/api/lib.zig | 10 ++--- src/api/services/{users.zig => actors.zig} | 39 ++++++------------ src/api/services/auth.zig | 46 +++++++++++++--------- src/api/services/invites.zig | 6 +-- src/main/migrations.zig | 28 ++++++------- 5 files changed, 62 insertions(+), 67 deletions(-) rename src/api/services/{users.zig => actors.zig} (79%) diff --git a/src/api/lib.zig b/src/api/lib.zig index eb3de00..487a4a9 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -7,7 +7,7 @@ const Uuid = util.Uuid; const services = struct { const communities = @import("./services/communities.zig"); - const users = @import("./services/users.zig"); + const actors = @import("./services/actors.zig"); const auth = @import("./services/auth.zig"); const invites = @import("./services/invites.zig"); const notes = @import("./services/notes.zig"); @@ -138,7 +138,7 @@ pub const ApiSource = struct { return Conn{ .db = db, .token_info = token_info, - .user_id = token_info.account_id, + .user_id = token_info.user_id, .community = community, .arena = arena, }; @@ -184,7 +184,7 @@ fn ApiConn(comptime DbConn: type) type { }; pub fn verifyAuthorization(self: *Self) !AuthorizationInfo { if (self.token_info) |info| { - const user = try services.users.get(self.db, info.account_id, self.arena.allocator()); + const user = try services.actors.get(self.db, info.user_id, self.arena.allocator()); return AuthorizationInfo{ .id = user.id, @@ -292,7 +292,7 @@ fn ApiConn(comptime DbConn: type) type { } pub fn getUser(self: *Self, user_id: Uuid) !UserResponse { - const user = try services.users.get(self.db, user_id, self.arena.allocator()); + const user = try services.actors.get(self.db, user_id, self.arena.allocator()); if (self.user_id == null) { if (!Uuid.eql(self.community.id, user.community_id)) return error.NotFound; @@ -322,7 +322,7 @@ fn ApiConn(comptime DbConn: type) type { pub fn getNote(self: *Self, note_id: Uuid) !NoteResponse { const note = try services.notes.get(self.db, note_id, self.arena.allocator()); - const user = try services.users.get(self.db, note.author_id, self.arena.allocator()); + const user = try services.actors.get(self.db, note.author_id, self.arena.allocator()); // Only serve community-specific notes on unauthenticated requests if (self.user_id == null) { diff --git a/src/api/services/users.zig b/src/api/services/actors.zig similarity index 79% rename from src/api/services/users.zig rename to src/api/services/actors.zig index 1aea67f..2c18254 100644 --- a/src/api/services/users.zig +++ b/src/api/services/actors.zig @@ -13,17 +13,6 @@ pub const CreateError = error{ DatabaseFailure, }; -pub const Kind = enum { - user, - admin, -}; - -pub const CreateOptions = struct { - invite_id: ?Uuid = null, - email: ?[]const u8 = null, - kind: Kind = .user, -}; - pub const LookupError = error{ DatabaseFailure, }; @@ -36,7 +25,7 @@ pub fn lookupByUsername( const row = db.queryRow( std.meta.Tuple(&.{Uuid}), \\SELECT id - \\FROM account + \\FROM actor \\WHERE username = $1 AND community_id = $2 \\LIMIT 1 , @@ -77,18 +66,16 @@ pub fn create( db: anytype, username: []const u8, community_id: Uuid, - kind: Kind, alloc: std.mem.Allocator, ) CreateError!Uuid { const id = Uuid.randV4(util.getThreadPrng()); try validateUsername(username); - db.insert("account", .{ + db.insert("actor", .{ .id = id, .username = username, .community_id = community_id, - .kind = kind, .created_at = DateTime.now(), }, alloc) catch |err| return switch (err) { error.UniqueViolation => error.UsernameTaken, @@ -98,31 +85,29 @@ pub fn create( return id; } -pub const User = struct { +pub const Actor = struct { id: Uuid, username: []const u8, host: []const u8, community_id: Uuid, - kind: Kind, created_at: DateTime, }; -pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) !User { +pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) !Actor { return db.queryRow( - User, + Actor, \\SELECT - \\ account.id, - \\ account.username, + \\ actor.id, + \\ actor.username, \\ community.host, - \\ account.community_id, - \\ account.kind, - \\ account.created_at - \\FROM account JOIN community - \\ ON account.community_id = community.id - \\WHERE account.id = $1 + \\ actor.community_id, + \\ actor.created_at + \\FROM actor JOIN community + \\ ON actor.community_id = community.id + \\WHERE actor.id = $1 \\LIMIT 1 , .{id}, diff --git a/src/api/services/auth.zig b/src/api/services/auth.zig index 61aac17..03a5feb 100644 --- a/src/api/services/auth.zig +++ b/src/api/services/auth.zig @@ -1,6 +1,6 @@ const std = @import("std"); const util = @import("util"); -const users = @import("./users.zig"); +const actors = @import("./actors.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; @@ -10,13 +10,17 @@ pub const RegistrationError = error{ DatabaseFailure, HashFailure, OutOfMemory, -} || users.CreateError; +} || actors.CreateError; pub const min_password_chars = 12; +pub const Kind = enum { + user, + admin, +}; pub const RegistrationOptions = struct { invite_id: ?Uuid = null, email: ?[]const u8 = null, - kind: users.Kind = .user, + kind: Kind = .user, }; /// Creates a local account with the given information and returns the @@ -31,17 +35,20 @@ pub fn register( ) RegistrationError!Uuid { if (password.len < min_password_chars) return error.PasswordTooShort; + // perform pre-validation to avoid having to hash the password if it fails + try actors.validateUsername(username); const hash = try hashPassword(password, alloc); defer alloc.free(hash); const tx = db.beginOrSavepoint() catch return error.DatabaseFailure; errdefer tx.rollback(); - const id = try users.create(tx, username, community_id, options.kind, alloc); - tx.insert("local_account", .{ - .account_id = id, + const id = try actors.create(tx, username, community_id, alloc); + tx.insert("account", .{ + .id = id, .invite_id = options.invite_id, .email = options.email, + .kind = options.kind, }, alloc) catch return error.DatabaseFailure; tx.insert("password", .{ .account_id = id, @@ -63,7 +70,7 @@ pub const LoginError = error{ pub const LoginResult = struct { token: []const u8, - account_id: Uuid, + user_id: Uuid, }; /// Attempts to login to the account `@username@community` and creates @@ -79,10 +86,12 @@ pub fn login( const info = db.queryRow( struct { account_id: Uuid, hash: []const u8 }, \\SELECT account.id as account_id, password.hash - \\FROM password JOIN account - \\ ON password.account_id = account.id - \\WHERE account.username = $1 - \\ AND account.community_id = $2 + \\FROM password + \\ JOIN account + \\ JOIN actor + \\ ON password.account_id = account.id AND account.id = actor.id + \\WHERE actor.username = $1 + \\ AND actor.community_id = $2 \\LIMIT 1 , .{ username, community_id }, @@ -92,7 +101,6 @@ pub fn login( else => error.DatabaseFailure, }; errdefer util.deepFree(alloc, info); - std.log.debug("got password", .{}); try verifyPassword(info.hash, password, alloc); @@ -134,13 +142,13 @@ pub fn login( return LoginResult{ .token = token, - .account_id = info.account_id, + .user_id = info.account_id, }; } pub const VerifyTokenError = error{ InvalidToken, DatabaseFailure, OutOfMemory }; pub const TokenInfo = struct { - account_id: Uuid, + user_id: Uuid, issued_at: DateTime, }; pub fn verifyToken( @@ -153,10 +161,12 @@ pub fn verifyToken( return db.queryRow( TokenInfo, - \\SELECT token.account_id, token.issued_at - \\FROM token JOIN account - \\ ON token.account_id = account.id - \\WHERE token.hash = $1 AND account.community_id = $2 + \\SELECT token.account_id as user_id, token.issued_at + \\FROM token + \\ JOIN account + \\ JOIN actor + \\ ON token.account_id = account.id AND account.id = actor.id + \\WHERE token.hash = $1 AND actor.community_id = $2 \\LIMIT 1 , .{ hash, community_id }, diff --git a/src/api/services/invites.zig b/src/api/services/invites.zig index 283d5f4..09a156c 100644 --- a/src/api/services/invites.zig +++ b/src/api/services/invites.zig @@ -116,9 +116,9 @@ fn doGetQuery( // it is calculated based on the number of accounts that were created // from it const query = std.fmt.comptimePrint( - \\SELECT {s}, COUNT(local_account.account_id) AS times_used - \\FROM invite LEFT OUTER JOIN local_account - \\ ON invite.id = local_account.invite_id + \\SELECT {s}, COUNT(account.id) AS times_used + \\FROM invite LEFT OUTER JOIN account + \\ ON invite.id = account.invite_id \\WHERE {s} \\GROUP BY invite.id \\LIMIT 1 diff --git a/src/main/migrations.zig b/src/main/migrations.zig index 0e9365e..595646e 100644 --- a/src/main/migrations.zig +++ b/src/main/migrations.zig @@ -68,19 +68,19 @@ const create_migration_table = // migrations into a single one. this will require db recreation const migrations: []const Migration = &.{ .{ - .name = "accounts", + .name = "accounts and actors", .up = - \\CREATE TABLE account( + \\CREATE TABLE actor( \\ id UUID NOT NULL PRIMARY KEY, \\ username TEXT NOT NULL, \\ - \\ kind TEXT NOT NULL CHECK (kind IN ('admin', 'user')), \\ created_at TIMESTAMPTZ NOT NULL \\); \\ - \\CREATE TABLE local_account( - \\ account_id UUID NOT NULL PRIMARY KEY REFERENCES account(id), + \\CREATE TABLE account( + \\ id UUID NOT NULL PRIMARY KEY REFERENCES actor(id), \\ + \\ kind TEXT NOT NULL CHECK (kind IN ('admin', 'user')), \\ email TEXT \\); \\ @@ -93,8 +93,8 @@ const migrations: []const Migration = &.{ , .down = \\DROP TABLE password; - \\DROP TABLE local_account; \\DROP TABLE account; + \\DROP TABLE actor; , }, .{ @@ -104,7 +104,7 @@ const migrations: []const Migration = &.{ \\ id UUID NOT NULL, \\ \\ content TEXT NOT NULL, - \\ author_id UUID NOT NULL REFERENCES account(id), + \\ author_id UUID NOT NULL REFERENCES actor(id), \\ \\ created_at TIMESTAMPTZ NOT NULL \\); @@ -117,7 +117,7 @@ const migrations: []const Migration = &.{ \\CREATE TABLE reaction( \\ id UUID NOT NULL PRIMARY KEY, \\ - \\ account_id UUID NOT NULL REFERENCES account(id), + \\ author_id UUID NOT NULL REFERENCES actor(id), \\ note_id UUID NOT NULL REFERENCES note(id), \\ \\ created_at TIMESTAMPTZ NOT NULL @@ -130,7 +130,7 @@ const migrations: []const Migration = &.{ .up = \\CREATE TABLE token( \\ hash TEXT NOT NULL PRIMARY KEY, - \\ account_id UUID NOT NULL REFERENCES local_account(id), + \\ account_id UUID NOT NULL REFERENCES account(id), \\ \\ issued_at TIMESTAMPTZ NOT NULL \\); @@ -145,7 +145,7 @@ const migrations: []const Migration = &.{ \\ \\ name TEXT NOT NULL, \\ code TEXT NOT NULL UNIQUE, - \\ created_by UUID NOT NULL REFERENCES local_account(id), + \\ created_by UUID NOT NULL REFERENCES account(id), \\ \\ max_uses INTEGER, \\ @@ -154,10 +154,10 @@ const migrations: []const Migration = &.{ \\ \\ kind TEXT NOT NULL CHECK (kind in ('system_user', 'community_owner', 'user')) \\); - \\ALTER TABLE local_account ADD COLUMN invite_id UUID REFERENCES invite(id); + \\ALTER TABLE account ADD COLUMN invite_id UUID REFERENCES invite(id); , .down = - \\ALTER TABLE local_account DROP COLUMN invite_id; + \\ALTER TABLE account DROP COLUMN invite_id; \\DROP TABLE invite; , }, @@ -175,12 +175,12 @@ const migrations: []const Migration = &.{ \\ \\ created_at TIMESTAMPTZ NOT NULL \\); - \\ALTER TABLE account ADD COLUMN community_id UUID REFERENCES community(id); + \\ALTER TABLE actor ADD COLUMN community_id UUID REFERENCES community(id); \\ALTER TABLE invite ADD COLUMN community_id UUID REFERENCES community(id); , .down = \\ALTER TABLE invite DROP COLUMN community_id; - \\ALTER TABLE account DROP COLUMN community_id; + \\ALTER TABLE actor DROP COLUMN community_id; \\DROP TABLE community; , },