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 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) {
|
||||||
|
|
|
@ -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},
|
|
@ -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 },
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
,
|
,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue