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 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) {

View File

@ -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},

View File

@ -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 },

View File

@ -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

View File

@ -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;
,
},