fediglam/src/api/methods/auth.zig

163 lines
5.9 KiB
Zig

const std = @import("std");
const util = @import("util");
const types = @import("../types.zig");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const RegistrationOptions = @import("../lib.zig").RegistrationOptions;
const UserResponse = @import("../lib.zig").UserResponse;
const Invite = @import("../lib.zig").Invite;
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);
if (maybe_invite) |invite| {
if (!Uuid.eql(invite.community_id, self.context.community.id)) return error.WrongCommunity;
if (!isInviteValid(invite)) return error.InvalidInvite;
}
const invite_kind = if (maybe_invite) |inv| inv.kind else .user;
if (self.context.community.kind == .admin) @panic("Unimplmented");
const user_id = try models.auth.register(
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) {
.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) {
error.NotFound => return error.Unexpected,
else => |e| return e,
};
errdefer util.deepFree(self.allocator, user);
try tx.commitOrRelease();
return user;
}
};
}
const TestDb = struct {
tx_level: usize = 0,
rolled_back: bool = false,
committed: bool = false,
fn beginOrSavepoint(self: *TestDb) !*TestDb {
self.tx_level += 1;
return self;
}
fn rollback(self: *TestDb) void {
self.rolled_back = true;
self.tx_level -= 1;
}
fn commitOrRelease(self: *TestDb) !void {
self.committed = true;
self.tx_level -= 1;
}
};
test "register" {
comptime var exp_code = "code";
comptime var exp_community = Uuid.parse("a210c035-c9e1-4361-82a2-aaeac8e40dc6") catch unreachable;
comptime var uid = Uuid.parse("6d951fcc-1c9f-497b-9c96-31dfb9873708") catch unreachable;
const MockSvc = struct {
const invites = struct {
fn getByCode(db: *TestDb, code: []const u8, community_id: Uuid, alloc: std.mem.Allocator) !Invite {
try std.testing.expectEqual(db.tx_level, 1);
try std.testing.expectEqualStrings(exp_code, code);
try std.testing.expectEqual(exp_community, community_id);
return try util.deepClone(alloc, Invite{
.id = Uuid.parse("eac18f43-4dcc-489f-9fb5-4c1633e7b4e0") catch unreachable,
.created_by = Uuid.parse("6d951fcc-1c9f-497b-9c96-31dfb9873708") catch unreachable,
.community_id = exp_community,
.name = "test invite",
.code = exp_code,
.kind = .user,
.created_at = DateTime.parse("2022-12-21T09:05:50Z") catch unreachable,
.times_used = 0,
.expires_at = null,
.max_uses = null,
});
}
};
const auth = struct {
fn register(
db: *TestDb,
username: []const u8,
password: []const u8,
community_id: Uuid,
_: @import("../services/auth.zig").RegistrationOptions,
_: std.mem.Allocator,
) anyerror!Uuid {
try std.testing.expectEqual(db.tx_level, 1);
try std.testing.expectEqualStrings("root", username);
try std.testing.expectEqualStrings("password", password);
try std.testing.expectEqual(exp_community, community_id);
return uid;
}
};
const actors = struct {
fn get(_: *TestDb, id: Uuid, alloc: std.mem.Allocator) anyerror!types.Actor {
try std.testing.expectEqual(uid, id);
return try util.deepClone(alloc, std.mem.zeroInit(types.Actor, .{
.id = id,
.username = "root",
.host = "example.com",
.community_id = exp_community,
}));
}
};
const communities = struct {
fn transferOwnership(_: *TestDb, _: Uuid, _: Uuid) anyerror!void {}
};
};
var db = TestDb{};
util.deepFree(std.testing.allocator, try methods(MockSvc).register(.{
.db = &db,
.allocator = std.testing.allocator,
.community = .{
.id = exp_community,
.kind = .local,
},
}, "root", "password", .{}));
try std.testing.expectEqual(false, db.rolled_back);
try std.testing.expectEqual(true, db.committed);
try std.testing.expectEqual(@as(usize, 0), db.tx_level);
}