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 {
|
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|
|
||||||
|
|
|
@ -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 {
|
|
||||||
fn isInviteValid(invite: Invite) bool {
|
|
||||||
if (invite.max_uses != null and invite.times_used >= invite.max_uses.?) return false;
|
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;
|
if (invite.expires_at != null and DateTime.now().isAfter(invite.expires_at.?)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pub fn register(self: anytype, username: []const u8, password: []const u8, opt: RegistrationOptions) !types.Actor {
|
|
||||||
const tx = try self.db.beginOrSavepoint();
|
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 maybe_invite = if (opt.invite_code) |code|
|
const maybe_invite = if (opt.invite_code) |code|
|
||||||
try models.invites.getByCode(tx, code, self.context.community.id, self.allocator)
|
try tx.getInviteByCode(alloc, code, ctx.community.id)
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
defer if (maybe_invite) |inv| util.deepFree(self.allocator, inv);
|
defer if (maybe_invite) |inv| util.deepFree(alloc, inv);
|
||||||
|
|
||||||
if (maybe_invite) |invite| {
|
if (maybe_invite) |invite| {
|
||||||
if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity;
|
if (!Uuid.eql(invite.community_id, ctx.community.id)) return error.WrongCommunity;
|
||||||
if (!isInviteValid(invite)) return error.InvalidInvite;
|
if (!isInviteValid(invite)) return error.InvalidInvite;
|
||||||
}
|
}
|
||||||
|
|
||||||
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
|
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
|
||||||
|
|
||||||
if (self.context.community.kind == .admin) @panic("Unimplmented");
|
if (ctx.community.kind == .admin) @panic("Unimplmented");
|
||||||
|
|
||||||
const user_id = try createLocalAccount(
|
const user_id = try createLocalAccount(
|
||||||
|
alloc,
|
||||||
tx,
|
tx,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
self.context.community.id,
|
ctx.community.id,
|
||||||
.{
|
.{
|
||||||
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
|
.invite_id = if (maybe_invite) |inv| @as(?Uuid, inv.id) else null,
|
||||||
.email = opt.email,
|
.email = opt.email,
|
||||||
},
|
},
|
||||||
self.allocator,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (invite_kind) {
|
switch (invite_kind) {
|
||||||
.user => {},
|
.user => {},
|
||||||
.system => @panic("System user invites unimplemented"),
|
.system => @panic("System user invites unimplemented"),
|
||||||
.community_owner => {
|
.community_owner => {
|
||||||
try models.communities.transferOwnership(tx, self.context.community.id, user_id);
|
try tx.transferCommunityOwnership(ctx.community.id, user_id);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = models.actors.get(tx, user_id, self.allocator) catch |err| switch (err) {
|
const user = tx.getActor(alloc, user_id) catch |err| switch (err) {
|
||||||
error.NotFound => return error.Unexpected,
|
error.NotFound => return error.Unexpected,
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
errdefer util.deepFree(self.allocator, user);
|
errdefer util.deepFree(alloc, user);
|
||||||
|
|
||||||
try tx.commitOrRelease();
|
try tx.commitTx();
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only for internal use
|
pub fn createLocalAccount(
|
||||||
pub fn createLocalAccount(
|
alloc: std.mem.Allocator,
|
||||||
db: anytype,
|
svcs: anytype,
|
||||||
username: []const u8,
|
username: []const u8,
|
||||||
password: []const u8,
|
password: []const u8,
|
||||||
community_id: Uuid,
|
community_id: Uuid,
|
||||||
opt: AccountCreateOptions,
|
opt: AccountCreateOptions,
|
||||||
alloc: std.mem.Allocator,
|
) !Uuid {
|
||||||
) !Uuid {
|
const tx = try svcs.beginTx();
|
||||||
const tx = try db.beginOrSavepoint();
|
errdefer tx.rollbackTx();
|
||||||
errdefer tx.rollback();
|
|
||||||
|
|
||||||
const hash = try hashPassword(password, alloc);
|
const hash = try hashPassword(password, alloc);
|
||||||
defer alloc.free(hash);
|
defer alloc.free(hash);
|
||||||
|
|
||||||
const id = try models.actors.create(tx, username, community_id, false, alloc);
|
const id = try tx.createActor(alloc, username, community_id, false);
|
||||||
try models.accounts.create(tx, id, hash, opt, alloc);
|
try tx.createAccount(alloc, id, hash, opt);
|
||||||
|
|
||||||
try tx.commitOrRelease();
|
try tx.commitTx();
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verifyToken(self: anytype, token: []const u8) !Token.Info {
|
pub fn verifyToken(alloc: std.mem.Allocator, ctx: ApiContext, svcs: anytype, token: []const u8) !Token.Info {
|
||||||
const hash = try hashToken(token, self.allocator);
|
const hash = try hashToken(token, alloc);
|
||||||
defer self.allocator.free(hash);
|
defer alloc.free(hash);
|
||||||
|
|
||||||
const info = try models.tokens.getByHash(self.db, hash, self.context.community.id, self.allocator);
|
const info = try svcs.getTokenByHash(alloc, hash, ctx.community.id);
|
||||||
defer util.deepFree(self.allocator, info);
|
defer util.deepFree(alloc, info);
|
||||||
|
|
||||||
return .{ .user_id = info.account_id, .issued_at = info.issued_at };
|
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 = .{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue