account + local_account -> actor + account

This commit is contained in:
jaina heartles 2022-10-11 20:06:29 -07:00
parent d852a3d153
commit b2c87dd207
5 changed files with 62 additions and 67 deletions

View file

@ -7,7 +7,7 @@ const Uuid = util.Uuid;
const services = struct { const services = struct {
const communities = @import("./services/communities.zig"); 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 auth = @import("./services/auth.zig");
const invites = @import("./services/invites.zig"); const invites = @import("./services/invites.zig");
const notes = @import("./services/notes.zig"); const notes = @import("./services/notes.zig");
@ -138,7 +138,7 @@ pub const ApiSource = struct {
return Conn{ return Conn{
.db = db, .db = db,
.token_info = token_info, .token_info = token_info,
.user_id = token_info.account_id, .user_id = token_info.user_id,
.community = community, .community = community,
.arena = arena, .arena = arena,
}; };
@ -184,7 +184,7 @@ fn ApiConn(comptime DbConn: type) type {
}; };
pub fn verifyAuthorization(self: *Self) !AuthorizationInfo { pub fn verifyAuthorization(self: *Self) !AuthorizationInfo {
if (self.token_info) |info| { 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{ return AuthorizationInfo{
.id = user.id, .id = user.id,
@ -292,7 +292,7 @@ fn ApiConn(comptime DbConn: type) type {
} }
pub fn getUser(self: *Self, user_id: Uuid) !UserResponse { 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 (self.user_id == null) {
if (!Uuid.eql(self.community.id, user.community_id)) return error.NotFound; 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 { pub fn getNote(self: *Self, note_id: Uuid) !NoteResponse {
const note = try services.notes.get(self.db, note_id, self.arena.allocator()); 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 // Only serve community-specific notes on unauthenticated requests
if (self.user_id == null) { if (self.user_id == null) {

View file

@ -13,17 +13,6 @@ pub const CreateError = error{
DatabaseFailure, 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{ pub const LookupError = error{
DatabaseFailure, DatabaseFailure,
}; };
@ -36,7 +25,7 @@ pub fn lookupByUsername(
const row = db.queryRow( const row = db.queryRow(
std.meta.Tuple(&.{Uuid}), std.meta.Tuple(&.{Uuid}),
\\SELECT id \\SELECT id
\\FROM account \\FROM actor
\\WHERE username = $1 AND community_id = $2 \\WHERE username = $1 AND community_id = $2
\\LIMIT 1 \\LIMIT 1
, ,
@ -77,18 +66,16 @@ pub fn create(
db: anytype, db: anytype,
username: []const u8, username: []const u8,
community_id: Uuid, community_id: Uuid,
kind: Kind,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
) CreateError!Uuid { ) CreateError!Uuid {
const id = Uuid.randV4(util.getThreadPrng()); const id = Uuid.randV4(util.getThreadPrng());
try validateUsername(username); try validateUsername(username);
db.insert("account", .{ db.insert("actor", .{
.id = id, .id = id,
.username = username, .username = username,
.community_id = community_id, .community_id = community_id,
.kind = kind,
.created_at = DateTime.now(), .created_at = DateTime.now(),
}, alloc) catch |err| return switch (err) { }, alloc) catch |err| return switch (err) {
error.UniqueViolation => error.UsernameTaken, error.UniqueViolation => error.UsernameTaken,
@ -98,31 +85,29 @@ pub fn create(
return id; return id;
} }
pub const User = struct { pub const Actor = struct {
id: Uuid, id: Uuid,
username: []const u8, username: []const u8,
host: []const u8, host: []const u8,
community_id: Uuid, community_id: Uuid,
kind: Kind,
created_at: DateTime, 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( return db.queryRow(
User, Actor,
\\SELECT \\SELECT
\\ account.id, \\ actor.id,
\\ account.username, \\ actor.username,
\\ community.host, \\ community.host,
\\ account.community_id, \\ actor.community_id,
\\ account.kind, \\ actor.created_at
\\ account.created_at \\FROM actor JOIN community
\\FROM account JOIN community \\ ON actor.community_id = community.id
\\ ON account.community_id = community.id \\WHERE actor.id = $1
\\WHERE account.id = $1
\\LIMIT 1 \\LIMIT 1
, ,
.{id}, .{id},

View file

@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const util = @import("util"); const util = @import("util");
const users = @import("./users.zig"); const actors = @import("./actors.zig");
const Uuid = util.Uuid; const Uuid = util.Uuid;
const DateTime = util.DateTime; const DateTime = util.DateTime;
@ -10,13 +10,17 @@ pub const RegistrationError = error{
DatabaseFailure, DatabaseFailure,
HashFailure, HashFailure,
OutOfMemory, OutOfMemory,
} || users.CreateError; } || actors.CreateError;
pub const min_password_chars = 12; pub const min_password_chars = 12;
pub const Kind = enum {
user,
admin,
};
pub const RegistrationOptions = struct { pub const RegistrationOptions = struct {
invite_id: ?Uuid = null, invite_id: ?Uuid = null,
email: ?[]const u8 = null, email: ?[]const u8 = null,
kind: users.Kind = .user, kind: Kind = .user,
}; };
/// Creates a local account with the given information and returns the /// Creates a local account with the given information and returns the
@ -31,17 +35,20 @@ pub fn register(
) RegistrationError!Uuid { ) RegistrationError!Uuid {
if (password.len < min_password_chars) return error.PasswordTooShort; 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); const hash = try hashPassword(password, alloc);
defer alloc.free(hash); defer alloc.free(hash);
const tx = db.beginOrSavepoint() catch return error.DatabaseFailure; const tx = db.beginOrSavepoint() catch return error.DatabaseFailure;
errdefer tx.rollback(); errdefer tx.rollback();
const id = try users.create(tx, username, community_id, options.kind, alloc); const id = try actors.create(tx, username, community_id, alloc);
tx.insert("local_account", .{ tx.insert("account", .{
.account_id = id, .id = id,
.invite_id = options.invite_id, .invite_id = options.invite_id,
.email = options.email, .email = options.email,
.kind = options.kind,
}, alloc) catch return error.DatabaseFailure; }, alloc) catch return error.DatabaseFailure;
tx.insert("password", .{ tx.insert("password", .{
.account_id = id, .account_id = id,
@ -63,7 +70,7 @@ pub const LoginError = error{
pub const LoginResult = struct { pub const LoginResult = struct {
token: []const u8, token: []const u8,
account_id: Uuid, user_id: Uuid,
}; };
/// Attempts to login to the account `@username@community` and creates /// Attempts to login to the account `@username@community` and creates
@ -79,10 +86,12 @@ pub fn login(
const info = db.queryRow( const info = db.queryRow(
struct { account_id: Uuid, hash: []const u8 }, struct { account_id: Uuid, hash: []const u8 },
\\SELECT account.id as account_id, password.hash \\SELECT account.id as account_id, password.hash
\\FROM password JOIN account \\FROM password
\\ ON password.account_id = account.id \\ JOIN account
\\WHERE account.username = $1 \\ JOIN actor
\\ AND account.community_id = $2 \\ ON password.account_id = account.id AND account.id = actor.id
\\WHERE actor.username = $1
\\ AND actor.community_id = $2
\\LIMIT 1 \\LIMIT 1
, ,
.{ username, community_id }, .{ username, community_id },
@ -92,7 +101,6 @@ pub fn login(
else => error.DatabaseFailure, else => error.DatabaseFailure,
}; };
errdefer util.deepFree(alloc, info); errdefer util.deepFree(alloc, info);
std.log.debug("got password", .{});
try verifyPassword(info.hash, password, alloc); try verifyPassword(info.hash, password, alloc);
@ -134,13 +142,13 @@ pub fn login(
return LoginResult{ return LoginResult{
.token = token, .token = token,
.account_id = info.account_id, .user_id = info.account_id,
}; };
} }
pub const VerifyTokenError = error{ InvalidToken, DatabaseFailure, OutOfMemory }; pub const VerifyTokenError = error{ InvalidToken, DatabaseFailure, OutOfMemory };
pub const TokenInfo = struct { pub const TokenInfo = struct {
account_id: Uuid, user_id: Uuid,
issued_at: DateTime, issued_at: DateTime,
}; };
pub fn verifyToken( pub fn verifyToken(
@ -153,10 +161,12 @@ pub fn verifyToken(
return db.queryRow( return db.queryRow(
TokenInfo, TokenInfo,
\\SELECT token.account_id, token.issued_at \\SELECT token.account_id as user_id, token.issued_at
\\FROM token JOIN account \\FROM token
\\ ON token.account_id = account.id \\ JOIN account
\\WHERE token.hash = $1 AND account.community_id = $2 \\ JOIN actor
\\ ON token.account_id = account.id AND account.id = actor.id
\\WHERE token.hash = $1 AND actor.community_id = $2
\\LIMIT 1 \\LIMIT 1
, ,
.{ hash, community_id }, .{ hash, community_id },

View file

@ -116,9 +116,9 @@ fn doGetQuery(
// it is calculated based on the number of accounts that were created // it is calculated based on the number of accounts that were created
// from it // from it
const query = std.fmt.comptimePrint( const query = std.fmt.comptimePrint(
\\SELECT {s}, COUNT(local_account.account_id) AS times_used \\SELECT {s}, COUNT(account.id) AS times_used
\\FROM invite LEFT OUTER JOIN local_account \\FROM invite LEFT OUTER JOIN account
\\ ON invite.id = local_account.invite_id \\ ON invite.id = account.invite_id
\\WHERE {s} \\WHERE {s}
\\GROUP BY invite.id \\GROUP BY invite.id
\\LIMIT 1 \\LIMIT 1

View file

@ -68,19 +68,19 @@ const create_migration_table =
// migrations into a single one. this will require db recreation // migrations into a single one. this will require db recreation
const migrations: []const Migration = &.{ const migrations: []const Migration = &.{
.{ .{
.name = "accounts", .name = "accounts and actors",
.up = .up =
\\CREATE TABLE account( \\CREATE TABLE actor(
\\ id UUID NOT NULL PRIMARY KEY, \\ id UUID NOT NULL PRIMARY KEY,
\\ username TEXT NOT NULL, \\ username TEXT NOT NULL,
\\ \\
\\ kind TEXT NOT NULL CHECK (kind IN ('admin', 'user')),
\\ created_at TIMESTAMPTZ NOT NULL \\ created_at TIMESTAMPTZ NOT NULL
\\); \\);
\\ \\
\\CREATE TABLE local_account( \\CREATE TABLE account(
\\ account_id UUID NOT NULL PRIMARY KEY REFERENCES account(id), \\ id UUID NOT NULL PRIMARY KEY REFERENCES actor(id),
\\ \\
\\ kind TEXT NOT NULL CHECK (kind IN ('admin', 'user')),
\\ email TEXT \\ email TEXT
\\); \\);
\\ \\
@ -93,8 +93,8 @@ const migrations: []const Migration = &.{
, ,
.down = .down =
\\DROP TABLE password; \\DROP TABLE password;
\\DROP TABLE local_account;
\\DROP TABLE account; \\DROP TABLE account;
\\DROP TABLE actor;
, ,
}, },
.{ .{
@ -104,7 +104,7 @@ const migrations: []const Migration = &.{
\\ id UUID NOT NULL, \\ id UUID NOT NULL,
\\ \\
\\ content TEXT 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 \\ created_at TIMESTAMPTZ NOT NULL
\\); \\);
@ -117,7 +117,7 @@ const migrations: []const Migration = &.{
\\CREATE TABLE reaction( \\CREATE TABLE reaction(
\\ id UUID NOT NULL PRIMARY KEY, \\ 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), \\ note_id UUID NOT NULL REFERENCES note(id),
\\ \\
\\ created_at TIMESTAMPTZ NOT NULL \\ created_at TIMESTAMPTZ NOT NULL
@ -130,7 +130,7 @@ const migrations: []const Migration = &.{
.up = .up =
\\CREATE TABLE token( \\CREATE TABLE token(
\\ hash TEXT NOT NULL PRIMARY KEY, \\ 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 \\ issued_at TIMESTAMPTZ NOT NULL
\\); \\);
@ -145,7 +145,7 @@ const migrations: []const Migration = &.{
\\ \\
\\ name TEXT NOT NULL, \\ name TEXT NOT NULL,
\\ code TEXT NOT NULL UNIQUE, \\ 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, \\ max_uses INTEGER,
\\ \\
@ -154,10 +154,10 @@ const migrations: []const Migration = &.{
\\ \\
\\ kind TEXT NOT NULL CHECK (kind in ('system_user', 'community_owner', 'user')) \\ 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 = .down =
\\ALTER TABLE local_account DROP COLUMN invite_id; \\ALTER TABLE account DROP COLUMN invite_id;
\\DROP TABLE invite; \\DROP TABLE invite;
, ,
}, },
@ -175,12 +175,12 @@ const migrations: []const Migration = &.{
\\ \\
\\ created_at TIMESTAMPTZ NOT NULL \\ 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); \\ALTER TABLE invite ADD COLUMN community_id UUID REFERENCES community(id);
, ,
.down = .down =
\\ALTER TABLE invite DROP COLUMN community_id; \\ALTER TABLE invite DROP COLUMN community_id;
\\ALTER TABLE account DROP COLUMN community_id; \\ALTER TABLE actor DROP COLUMN community_id;
\\DROP TABLE community; \\DROP TABLE community;
, ,
}, },