From 9774f214f36e52bdb34e0af6ef028f3b7f48781a Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Mon, 2 Jan 2023 17:17:42 -0800 Subject: [PATCH] add stubs to services.zig --- src/api/lib.zig | 42 +++++--- src/api/methods/auth.zig | 175 +++++++++++++++++-------------- src/api/services.zig | 50 ++++++++- src/api/services/actors.zig | 2 +- src/api/services/communities.zig | 2 +- src/api/services/invites.zig | 2 +- 6 files changed, 173 insertions(+), 100 deletions(-) diff --git a/src/api/lib.zig b/src/api/lib.zig index c9c7901..5f0ddcf 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -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| diff --git a/src/api/methods/auth.zig b/src/api/methods/auth.zig index 27f4543..2a7316c 100644 --- a/src/api/methods/auth.zig +++ b/src/api/methods/auth.zig @@ -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 = .{ diff --git a/src/api/services.zig b/src/api/services.zig index dad0506..cee6c94 100644 --- a/src/api/services.zig +++ b/src/api/services.zig @@ -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, diff --git a/src/api/services/actors.zig b/src/api/services/actors.zig index 95bfe10..9c6dab2 100644 --- a/src/api/services/actors.zig +++ b/src/api/services/actors.zig @@ -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, diff --git a/src/api/services/communities.zig b/src/api/services/communities.zig index 6b575d5..216be86 100644 --- a/src/api/services/communities.zig +++ b/src/api/services/communities.zig @@ -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); diff --git a/src/api/services/invites.zig b/src/api/services/invites.zig index fe0ce6f..a9eddb8 100644 --- a/src/api/services/invites.zig +++ b/src/api/services/invites.zig @@ -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;