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 { 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(); const svc = @import("./services.zig").Services(sql.Db){ .db = db };
errdefer tx.rollback(); const tx = try svc.beginTx();
errdefer tx.rollbackTx();
var arena = std.heap.ArenaAllocator.init(allocator); var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit(); defer arena.deinit();
try tx.setConstraintMode(.deferred); const community_id = try tx.createCommunity(
arena.allocator(),
const community_id = try services.communities.create(
tx,
origin, origin,
.{ .name = "Cluster Admin", .kind = .admin }, .{ .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, tx,
username, username,
password, password,
community_id, community_id,
.{ .role = .admin }, .{ .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( std.log.info(
"Created admin user {s} (id {}) with cluster admin origin {s} (id {})", "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 { pub fn connectToken(self: *ApiSource, host: []const u8, token: []const u8, alloc: std.mem.Allocator) !Conn {
var conn = try self.connectUnauthorized(host, alloc); var conn = try self.connectUnauthorized(host, alloc);
errdefer conn.close(); 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; return conn;
} }
}; };
@ -461,11 +459,27 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type {
return true; 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 { 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 { // pub fn register(self: *Self, username: []const u8, password: []const u8, opt: RegistrationOptions) !UserResponse {
// const tx = try self.db.beginOrSavepoint(); // const tx = try self.db.beginOrSavepoint();
// const maybe_invite = if (opt.invite_code) |code| // 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 const AccountCreateOptions = @import("../services/accounts.zig").CreateOptions;
pub fn methods(comptime models: type) type { fn isInviteValid(invite: Invite) bool {
return struct { if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false;
fn isInviteValid(invite: Invite) bool { if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false; return true;
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);
if (maybe_invite) |invite| { pub fn register(
if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity; alloc: std.mem.Allocator,
if (!isInviteValid(invite)) return error.InvalidInvite; 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( const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
tx,
username,
password,
self.context.community.id,
.{
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
.email = opt.email,
},
self.allocator,
);
switch (invite_kind) { if (ctx.community.kind == .admin) @panic("Unimplmented");
.user => {},
.system => @panic("System user invites unimplemented"),
.community_owner => {
try models.communities.transferOwnership(tx, self.context.community.id, user_id);
},
}
const user = models.actors.get(tx, user_id, self.allocator) catch |err| switch (err) { const user_id = try createLocalAccount(
error.NotFound => return error.Unexpected, alloc,
else => |e| return e, tx,
}; username,
errdefer util.deepFree(self.allocator, user); password,
ctx.community.id,
.{
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
.email = opt.email,
},
);
try tx.commitOrRelease(); switch (invite_kind) {
return user; .user => {},
} .system => @panic("System user invites unimplemented"),
.community_owner => {
try tx.transferCommunityOwnership(ctx.community.id, user_id);
},
}
// Only for internal use const user = tx.getActor(alloc, user_id) catch |err| switch (err) {
pub fn createLocalAccount( error.NotFound => return error.Unexpected,
db: anytype, else => |e| return e,
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 };
}
}; };
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; 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 community_id = ctx.community.id;
const credentials = try svcs.getCredentialsByUsername( const credentials = try svcs.getCredentialsByUsername(
alloc, 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.createToken(alloc, credentials.account_id, token_hash);
try tx.commitTx(); 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); defer util.deepFree(alloc, info);
return .{ return .{
@ -312,7 +323,9 @@ test "register" {
}; };
var db = TestDb{}; var db = TestDb{};
util.deepFree(std.testing.allocator, try methods(MockSvc).register(.{
_ = MockSvc;
util.deepFree(std.testing.allocator, try register(.{
.db = &db, .db = &db,
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
.community = .{ .community = .{

View File

@ -14,9 +14,11 @@ const follows = @import("./services/follows.zig");
const accounts = @import("./services/accounts.zig"); const accounts = @import("./services/accounts.zig");
const tokens = @import("./services/tokens.zig"); const tokens = @import("./services/tokens.zig");
pub const Token = tokens.Token;
pub const Account = accounts.Account; pub const Account = accounts.Account;
pub const Credentials = accounts.Credentials; 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 { pub fn Services(comptime Db: type) type {
return struct { return struct {
@ -44,7 +46,7 @@ pub fn Services(comptime Db: type) type {
actor: Uuid, actor: Uuid,
password_hash: []const u8, password_hash: []const u8,
options: accounts.CreateOptions, options: accounts.CreateOptions,
) !Account { ) !void {
return try accounts.create(self.db, actor, password_hash, options, alloc); 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); 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( pub fn createToken(
self: Self, self: Self,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,

View File

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

View File

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

View File

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