diff --git a/src/api/lib.zig b/src/api/lib.zig index 30a2b58..c9c7901 100644 --- a/src/api/lib.zig +++ b/src/api/lib.zig @@ -318,6 +318,10 @@ pub const ApiContext = struct { pub fn userId(self: ApiContext) ?Uuid { if (self.token_info) |t| return t.user_id else return null; } + + pub fn isAdmin(self: ApiContext) bool { + return self.userId() != null and self.community.kind == .admin; + } }; fn ApiConn(comptime DbConn: type, comptime models: anytype) type { @@ -457,6 +461,10 @@ fn ApiConn(comptime DbConn: type, comptime models: anytype) type { return true; } + 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); + } + 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(); diff --git a/src/api/methods/auth.zig b/src/api/methods/auth.zig index 2847953..27f4543 100644 --- a/src/api/methods/auth.zig +++ b/src/api/methods/auth.zig @@ -91,57 +91,6 @@ pub fn methods(comptime models: type) type { return id; } - pub fn login(self: anytype, username: []const u8, password: []const u8) !Token { - const community_id = self.context.community.id; - const credentials = try models.accounts.getCredentialsByUsername( - self.db, - username, - community_id, - self.allocator, - ); - defer util.deepFree(self.allocator, credentials); - - try verifyPassword(credentials.password_hash, password, self.allocator); - - const token = try generateToken(self.allocator); - errdefer util.deepFree(self.allocator, token); - const token_hash = hashToken(token, self.allocator) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => unreachable, - }; - defer util.deepFree(self.allocator, token_hash); - - const tx = try self.db.begin(); - errdefer tx.rollback(); - - // ensure that the password has not changed in the meantime - { - const updated_info = try models.accounts.getCredentialsByUsername( - tx, - username, - community_id, - self.allocator, - ); - defer util.deepFree(self.allocator, updated_info); - - if (!std.mem.eql(u8, credentials.password_hash, updated_info.password_hash)) return error.InvalidLogin; - } - - try models.tokens.create(tx, credentials.account_id, token_hash, self.allocator); - - try tx.commit(); - const info = try models.tokens.getByHash(self.db, token_hash, community_id, self.allocator); - defer util.deepFree(self.allocator, info); - - return .{ - .value = token, - .info = .{ - .user_id = info.account_id, - .issued_at = info.issued_at, - }, - }; - } - pub fn verifyToken(self: anytype, token: []const u8) !Token.Info { const hash = try hashToken(token, self.allocator); defer self.allocator.free(hash); @@ -154,6 +103,56 @@ pub fn methods(comptime models: type) type { }; } +const ApiContext = @import("../lib.zig").ApiContext; +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, + username, + community_id, + ); + defer util.deepFree(alloc, credentials); + + try verifyPassword(credentials.password_hash, password, alloc); + + const token = try generateToken(alloc); + errdefer util.deepFree(alloc, token); + const token_hash = hashToken(token, alloc) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => unreachable, + }; + defer util.deepFree(alloc, token_hash); + + const tx = try svcs.beginTx(); + errdefer tx.rollbackTx(); + + // ensure that the password has not changed in the meantime + { + const updated_info = try tx.getCredentialsByUsername( + alloc, + username, + community_id, + ); + defer util.deepFree(alloc, updated_info); + + if (!std.mem.eql(u8, credentials.password_hash, updated_info.password_hash)) return error.InvalidLogin; + } + + try tx.createToken(alloc, credentials.account_id, token_hash); + + try tx.commitTx(); + const info = try tx.getTokenByHash(alloc, token_hash, community_id); + defer util.deepFree(alloc, info); + + return .{ + .value = token, + .info = .{ + .user_id = info.account_id, + .issued_at = info.issued_at, + }, + }; +} + // We use scrypt, a password hashing algorithm that attempts to slow down // GPU-based cracking approaches by using large amounts of memory, for // password hashing. diff --git a/src/api/services.zig b/src/api/services.zig new file mode 100644 index 0000000..dad0506 --- /dev/null +++ b/src/api/services.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const util = @import("util"); + +const Uuid = util.Uuid; +const DateTime = util.DateTime; + +const communities = @import("./services/communities.zig"); +const actors = @import("./services/actors.zig"); +const drive = @import("./services/drive.zig"); +const files = @import("./services/files.zig"); +const invites = @import("./services/invites.zig"); +const notes = @import("./services/notes.zig"); +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 fn Services(comptime Db: type) type { + return struct { + const Self = @This(); + + db: Db, + + pub fn beginTx(self: Self) !Services(Db.BeginOrSavepoint) { + return Services(Db.BeginOrSavepoint){ + .db = try self.db.beginOrSavepoint(), + }; + } + + pub fn commitTx(self: Self) !void { + return try self.db.commitOrRelease(); + } + + pub fn rollbackTx(self: Self) void { + return self.db.rollback(); + } + + pub fn createAccount( + self: Self, + alloc: std.mem.Allocator, + actor: Uuid, + password_hash: []const u8, + options: accounts.CreateOptions, + ) !Account { + return try accounts.create(self.db, actor, password_hash, options, alloc); + } + + pub fn getCredentialsByUsername( + self: Self, + alloc: std.mem.Allocator, + username: []const u8, + community_id: Uuid, + ) !Credentials { + return try accounts.getCredentialsByUsername(self.db, username, community_id, alloc); + } + + pub fn createToken( + self: Self, + alloc: std.mem.Allocator, + account_id: Uuid, + hash: []const u8, + ) !void { + return try tokens.create(self.db, account_id, hash, alloc); + } + + pub fn getTokenByHash( + self: Self, + alloc: std.mem.Allocator, + hash: []const u8, + community_id: Uuid, + ) !Token { + return try tokens.getByHash(self.db, hash, community_id, alloc); + } + }; +} diff --git a/src/sql/lib.zig b/src/sql/lib.zig index 776b159..bb55dd3 100644 --- a/src/sql/lib.zig +++ b/src/sql/lib.zig @@ -505,6 +505,7 @@ fn Tx(comptime tx_level: u8) type { }; } + pub const BeginOrSavepoint = Tx(tx_level + 1); pub const beginOrSavepoint = if (tx_level == 0) begin else savepoint; pub const commitOrRelease = if (tx_level < 2) commit else release;