account + local_account -> actor + account
This commit is contained in:
parent
d852a3d153
commit
b2c87dd207
5 changed files with 62 additions and 67 deletions
|
@ -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) {
|
||||
|
|
|
@ -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},
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue