add stubs to services.zig

This commit is contained in:
jaina heartles 2023-01-02 17:17:42 -08:00
parent 494d317ac1
commit 9774f214f3
6 changed files with 173 additions and 100 deletions

View file

@ -243,32 +243,30 @@ pub fn isAdminSetup(db: sql.Db) !bool {
}
pub fn setupAdmin(db: sql.Db, origin: []const u8, username: []const u8, password: []const u8, allocator: std.mem.Allocator) anyerror!void {
const tx = try db.begin();
errdefer tx.rollback();
const svc = @import("./services.zig").Services(sql.Db){ .db = db };
const tx = try svc.beginTx();
errdefer tx.rollbackTx();
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
try tx.setConstraintMode(.deferred);
const community_id = try services.communities.create(
tx,
const community_id = try tx.createCommunity(
arena.allocator(),
origin,
.{ .name = "Cluster Admin", .kind = .admin },
arena.allocator(),
);
const user = try @import("./methods/auth.zig").methods(services).createLocalAccount(
const user = try @import("./methods/auth.zig").createLocalAccount(
arena.allocator(),
tx,
username,
password,
community_id,
.{ .role = .admin },
arena.allocator(),
);
try services.communities.transferOwnership(tx, community_id, user);
try tx.transferCommunityOwnership(community_id, user);
try tx.commit();
try tx.commitTx();
std.log.info(
"Created admin user {s} (id {}) with cluster admin origin {s} (id {})",
@ -306,7 +304,7 @@ pub const ApiSource = struct {
pub fn connectToken(self: *ApiSource, host: []const u8, token: []const u8, alloc: std.mem.Allocator) !Conn {
var conn = try self.connectUnauthorized(host, alloc);
errdefer conn.close();
conn.context.token_info = try conn.verifyToken(token);
conn.context.token_info = try @import("./methods/auth.zig").verifyToken(alloc, conn.context, conn.getServices(), token);
return conn;
}
};
@ -461,11 +459,27 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type {
return true;
}
const Services = @import("./services.zig").Services(DbConn);
fn getServices(self: *Self) Services {
return Services{ .db = self.db };
}
pub fn login(self: *Self, username: []const u8, password: []const u8) !Token {
return @import("./methods/auth.zig").login(self.allocator, self.context, @import("./services.zig").Services(DbConn){ .db = self.db }, username, password);
return methods.auth.login(self.allocator, self.context, self.getServices(), username, password);
}
pub usingnamespace @import("./methods/auth.zig").methods(models);
const methods = struct {
const auth = @import("./methods/auth.zig");
};
pub fn register(
self: *Self,
username: []const u8,
password: []const u8,
opt: methods.auth.RegistrationOptions,
) !types.Actor {
return methods.auth.register(self.allocator, self.context, self.getServices(), username, password, opt);
}
//pub usingnamespace @import("./methods/auth.zig").methods(models);
// pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse {
// const tx = try self.db.beginOrSavepoint();
// const maybe_invite = if (opt.invite_code) |code|

View file

@ -14,97 +14,108 @@ pub const RegistrationOptions = struct {
pub const AccountCreateOptions = @import("../services/accounts.zig").CreateOptions;
pub fn methods(comptime models: type) type {
return struct {
fn isInviteValid(invite: Invite) bool {
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false;
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
return true;
}
pub fn register(self: anytype, username: []const u8, password: []const u8, opt: RegistrationOptions) !types.Actor {
const tx = try self.db.beginOrSavepoint();
const maybe_invite = if (opt.invite_code) |code|
try models.invites.getByCode(tx, code, self.context.community.id, self.allocator)
else
null;
defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv);
fn isInviteValid(invite: Invite) bool {
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false;
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
return true;
}
if (maybe_invite) |invite| {
if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity;
if (!isInviteValid(invite)) return error.InvalidInvite;
}
pub fn register(
alloc: std.mem.Allocator,
ctx: ApiContext,
svcs: anytype,
username: []const u8,
password: []const u8,
opt: RegistrationOptions,
) !types.Actor {
const tx = try svcs.beginTx();
errdefer tx.rollbackTx();
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
const maybe_invite = if (opt.invite_code) |code|
try tx.getInviteByCode(alloc, code, ctx.community.id)
else
null;
defer if (maybe_invite) |inv| util.deepFree(alloc, inv);
if (self.context.community.kind == .admin) @panic("Unimplmented");
if (maybe_invite) |invite| {
if (!Uuid.eql(invite.community_id, ctx.community.id)) return error.WrongCommunity;
if (!isInviteValid(invite)) return error.InvalidInvite;
}
const user_id = try createLocalAccount(
tx,
username,
password,
self.context.community.id,
.{
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
.email = opt.email,
},
self.allocator,
);
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
switch (invite_kind) {
.user => {},
.system => @panic("System user invites unimplemented"),
.community_owner => {
try models.communities.transferOwnership(tx, self.context.community.id, user_id);
},
}
if (ctx.community.kind == .admin) @panic("Unimplmented");
const user = models.actors.get(tx, user_id, self.allocator) catch |err| switch (err) {
error.NotFound => return error.Unexpected,
else => |e| return e,
};
errdefer util.deepFree(self.allocator, user);
const user_id = try createLocalAccount(
alloc,
tx,
username,
password,
ctx.community.id,
.{
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
.email = opt.email,
},
);
try tx.commitOrRelease();
return user;
}
switch (invite_kind) {
.user => {},
.system => @panic("System user invites unimplemented"),
.community_owner => {
try tx.transferCommunityOwnership(ctx.community.id, user_id);
},
}
// Only for internal use
pub fn createLocalAccount(
db: anytype,
username: []const u8,
password: []const u8,
community_id: Uuid,
opt: AccountCreateOptions,
alloc: std.mem.Allocator,
) !Uuid {
const tx = try db.beginOrSavepoint();
errdefer tx.rollback();
const hash = try hashPassword(password, alloc);
defer alloc.free(hash);
const id = try models.actors.create(tx, username, community_id, false, alloc);
try models.accounts.create(tx, id, hash, opt, alloc);
try tx.commitOrRelease();
return id;
}
pub fn verifyToken(self: anytype, token: []const u8) !Token.Info {
const hash = try hashToken(token, self.allocator);
defer self.allocator.free(hash);
const info = try models.tokens.getByHash(self.db, hash, self.context.community.id, self.allocator);
defer util.deepFree(self.allocator, info);
return .{ .user_id = info.account_id, .issued_at = info.issued_at };
}
const user = tx.getActor(alloc, user_id) catch |err| switch (err) {
error.NotFound => return error.Unexpected,
else => |e| return e,
};
errdefer util.deepFree(alloc, user);
try tx.commitTx();
return user;
}
pub fn createLocalAccount(
alloc: std.mem.Allocator,
svcs: anytype,
username: []const u8,
password: []const u8,
community_id: Uuid,
opt: AccountCreateOptions,
) !Uuid {
const tx = try svcs.beginTx();
errdefer tx.rollbackTx();
const hash = try hashPassword(password, alloc);
defer alloc.free(hash);
const id = try tx.createActor(alloc, username, community_id, false);
try tx.createAccount(alloc, id, hash, opt);
try tx.commitTx();
return id;
}
pub fn verifyToken(alloc: std.mem.Allocator, ctx: ApiContext, svcs: anytype, token: []const u8) !Token.Info {
const hash = try hashToken(token, alloc);
defer alloc.free(hash);
const info = try svcs.getTokenByHash(alloc, hash, ctx.community.id);
defer util.deepFree(alloc, info);
return .{ .user_id = info.account_id, .issued_at = info.issued_at };
}
const ApiContext = @import("../lib.zig").ApiContext;
pub fn login(alloc: std.mem.Allocator, ctx: ApiContext, svcs: anytype, username: []const u8, password: []const u8) !Token {
pub fn login(
alloc: std.mem.Allocator,
ctx: ApiContext,
svcs: anytype,
username: []const u8,
password: []const u8,
) !Token {
const community_id = ctx.community.id;
const credentials = try svcs.getCredentialsByUsername(
alloc,
@ -141,7 +152,7 @@ pub fn login(alloc: std.mem.Allocator, ctx: ApiContext, svcs: anytype, username:
try tx.createToken(alloc, credentials.account_id, token_hash);
try tx.commitTx();
const info = try tx.getTokenByHash(alloc, token_hash, community_id);
const info = try svcs.getTokenByHash(alloc, token_hash, community_id);
defer util.deepFree(alloc, info);
return .{
@ -312,7 +323,9 @@ test "register" {
};
var db = TestDb{};
util.deepFree(std.testing.allocator, try methods(MockSvc).register(.{
_ = MockSvc;
util.deepFree(std.testing.allocator, try register(.{
.db = &db,
.allocator = std.testing.allocator,
.community = .{

View file

@ -14,9 +14,11 @@ const follows = @import("./services/follows.zig");
const accounts = @import("./services/accounts.zig");
const tokens = @import("./services/tokens.zig");
pub const Token = tokens.Token;
pub const Account = accounts.Account;
pub const Credentials = accounts.Credentials;
pub const Actor = actors.Actor;
pub const Invite = invites.Invite;
pub const Token = tokens.Token;
pub fn Services(comptime Db: type) type {
return struct {
@ -44,7 +46,7 @@ pub fn Services(comptime Db: type) type {
actor: Uuid,
password_hash: []const u8,
options: accounts.CreateOptions,
) !Account {
) !void {
return try accounts.create(self.db, actor, password_hash, options, alloc);
}
@ -57,6 +59,50 @@ pub fn Services(comptime Db: type) type {
return try accounts.getCredentialsByUsername(self.db, username, community_id, alloc);
}
pub fn createActor(
self: Self,
alloc: std.mem.Allocator,
username: []const u8,
community_id: Uuid,
lax_username: bool, // TODO: remove this
) !Uuid {
return try actors.create(self.db, username, community_id, lax_username, alloc);
}
pub fn getActor(
self: Self,
alloc: std.mem.Allocator,
user_id: Uuid,
) !Actor {
return try actors.get(self.db, user_id, alloc);
}
pub fn createCommunity(
self: Self,
alloc: std.mem.Allocator,
origin: []const u8,
options: communities.CreateOptions,
) !Uuid {
return try communities.create(self.db, origin, options, alloc);
}
pub fn transferCommunityOwnership(
self: Self,
community_id: Uuid,
owner_id: Uuid,
) !void {
return try communities.transferOwnership(self.db, community_id, owner_id);
}
pub fn getInviteByCode(
self: Self,
alloc: std.mem.Allocator,
code: []const u8,
community_id: Uuid,
) !Invite {
return try invites.getByCode(self.db, code, community_id, alloc);
}
pub fn createToken(
self: Self,
alloc: std.mem.Allocator,

View file

@ -7,7 +7,7 @@ const types = @import("../types.zig");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const Actor = types.Actor;
pub const Actor = types.Actor;
pub const CreateError = error{
UsernameTaken,

View file

@ -8,7 +8,7 @@ const types = @import("../types.zig");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const Community = types.Community;
const CreateOptions = Community.CreateOptions;
pub const CreateOptions = Community.CreateOptions;
const QueryArgs = Community.QueryArgs;
const QueryResult = types.QueryResult(Community);

View file

@ -5,7 +5,7 @@ const types = @import("../types.zig");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const Invite = types.Invite;
pub const Invite = types.Invite;
// 9 random bytes = 12 random b64
const rand_len = 8;