add stubs to services.zig
This commit is contained in:
parent
494d317ac1
commit
9774f214f3
6 changed files with 173 additions and 100 deletions
|
@ -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|
|
||||
|
|
|
@ -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 = .{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue