diff options
Diffstat (limited to 'src/bun.js/api/bun.zig')
-rw-r--r-- | src/bun.js/api/bun.zig | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 3e09075e7..78a7379c2 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -1632,6 +1632,803 @@ pub const Crypto = struct { return ZigString.fromUTF8(error_message).toErrorInstance(globalThis); } + const unknwon_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")"; + + pub const PasswordObject = struct { + pub const pwhash = std.crypto.pwhash; + pub const Algorithm = enum { + argon2i, + argon2d, + argon2id, + bcrypt, + + pub const Value = union(Algorithm) { + argon2i: Argon2Params, + argon2d: Argon2Params, + argon2id: Argon2Params, + // bcrypt only accepts "cost" + bcrypt: u6, + + pub const bcrpyt_default = 10; + + pub const default = Algorithm.Value{ + .argon2id = .{}, + }; + + pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) ?Value { + if (value.isObject()) { + if (value.getTruthy(globalObject, "algorithm")) |algorithm_value| { + if (!algorithm_value.isString()) { + globalObject.throwInvalidArgumentType("hash", "algorithm", "string"); + return null; + } + + const algorithm_string = algorithm_value.getZigString(globalObject); + + switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse { + globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message); + return null; + }) { + .bcrypt => { + var algorithm = PasswordObject.Algorithm.Value{ + .bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default, + }; + + if (value.getTruthy(globalObject, "cost")) |rounds_value| { + if (!rounds_value.isNumber()) { + globalObject.throwInvalidArgumentType("hash", "cost", "number"); + return null; + } + + const rounds = rounds_value.coerce(i32, globalObject); + + if (rounds < 4 or rounds > 31) { + globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{}); + return null; + } + + algorithm.bcrypt = @intCast(u6, rounds); + } + + return algorithm; + }, + inline .argon2id, .argon2d, .argon2i => |tag| { + var argon = Algorithm.Argon2Params{}; + + if (value.getTruthy(globalObject, "timeCost")) |time_value| { + if (!time_value.isNumber()) { + globalObject.throwInvalidArgumentType("hash", "timeCost", "number"); + return null; + } + + const time_cost = time_value.coerce(i32, globalObject); + + if (time_cost < 1) { + globalObject.throwInvalidArguments("Time cost must be greater than 0", .{}); + return null; + } + + argon.time_cost = @intCast(u32, time_cost); + } + + if (value.getTruthy(globalObject, "memoryCost")) |memory_value| { + if (!memory_value.isNumber()) { + globalObject.throwInvalidArgumentType("hash", "memoryCost", "number"); + return null; + } + + const memory_cost = memory_value.coerce(i32, globalObject); + + if (memory_cost < 1) { + globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{}); + return null; + } + + argon.memory_cost = @intCast(u32, memory_cost); + } + + return @unionInit(Algorithm.Value, @tagName(tag), argon); + }, + } + + unreachable; + } else { + globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string"); + return null; + } + } else if (value.isString()) { + const algorithm_string = value.getZigString(globalObject); + + switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse { + globalObject.throwInvalidArgumentType("hash", "algorithm", unknwon_password_algorithm_message); + return null; + }) { + .bcrypt => { + return PasswordObject.Algorithm.Value{ + .bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default, + }; + }, + .argon2id => { + return PasswordObject.Algorithm.Value{ + .argon2id = .{}, + }; + }, + .argon2d => { + return PasswordObject.Algorithm.Value{ + .argon2d = .{}, + }; + }, + .argon2i => { + return PasswordObject.Algorithm.Value{ + .argon2i = .{}, + }; + }, + } + } else { + globalObject.throwInvalidArgumentType("hash", "algorithm", "string"); + return null; + } + + unreachable; + } + }; + + pub const Argon2Params = struct { + // we don't support the other options right now, but can add them later if someone asks + memory_cost: u32 = pwhash.argon2.Params.interactive_2id.m, + time_cost: u32 = pwhash.argon2.Params.interactive_2id.t, + + pub fn toParams(this: Argon2Params) pwhash.argon2.Params { + return pwhash.argon2.Params{ + .t = this.time_cost, + .m = this.memory_cost, + .p = 1, + }; + } + }; + + pub const argon2 = Algorithm.argon2id; + + pub const label = bun.ComptimeStringMap( + Algorithm, + .{ + .{ "argon2i", .argon2i }, + .{ "argon2d", .argon2d }, + .{ "argon2id", .argon2id }, + .{ "bcrypt", .bcrypt }, + }, + ); + + pub const default = Algorithm.argon2; + + pub fn get(pw: []const u8) ?Algorithm { + if (pw[0] != '$') { + return null; + } + + // PHC format looks like $<algorithm>$<params>$<salt>$<hash><optional stuff> + if (strings.hasPrefixComptime(pw[1..], "argon2d$")) { + return .argon2d; + } + if (strings.hasPrefixComptime(pw[1..], "argon2i$")) { + return .argon2i; + } + if (strings.hasPrefixComptime(pw[1..], "argon2id$")) { + return .argon2id; + } + + if (strings.hasPrefixComptime(pw[1..], "bcrypt")) { + return .bcrypt; + } + + // https://en.wikipedia.org/wiki/Crypt_(C) + if (strings.hasPrefixComptime(pw[1..], "2")) { + return .bcrypt; + } + + return null; + } + }; + + pub const HashError = pwhash.Error || error{UnsupportedAlgorithm}; + + // This is purposely simple because nobody asked to make it more complicated + pub fn hash( + allocator: std.mem.Allocator, + password: []const u8, + algorithm: Algorithm.Value, + ) HashError![]const u8 { + switch (algorithm) { + inline .argon2i, .argon2d, .argon2id => |argon| { + var outbuf: [4096]u8 = undefined; + const hash_options = pwhash.argon2.HashOptions{ + .params = argon.toParams(), + .allocator = allocator, + .mode = switch (algorithm) { + .argon2i => .argon2i, + .argon2d => .argon2d, + .argon2id => .argon2id, + else => unreachable, + }, + .encoding = .phc, + }; + // warning: argon2's code may spin up threads if paralellism is set to > 0 + // we don't expose this option + // but since it parses from phc format, it's possible that it will be set + // eventually we should do something that about that. + const out_bytes = try pwhash.argon2.strHash(password, hash_options, &outbuf); + return try allocator.dupe(u8, out_bytes); + }, + .bcrypt => |cost| { + var outbuf: [4096]u8 = undefined; + var outbuf_slice: []u8 = outbuf[0..]; + var password_to_use = password; + // bcrypt silently truncates passwords longer than 72 bytes + // we use SHA512 to hash the password if it's longer than 72 bytes + if (password.len > 72) { + var sha_256 = bun.sha.SHA512.init(); + sha_256.update(password); + sha_256.final(outbuf[0..bun.sha.SHA512.digest]); + password_to_use = outbuf[0..bun.sha.SHA512.digest]; + outbuf_slice = outbuf[bun.sha.SHA512.digest..]; + } + + const hash_options = pwhash.bcrypt.HashOptions{ + .params = pwhash.bcrypt.Params{ .rounds_log = cost }, + .allocator = allocator, + .encoding = .crypt, + }; + const out_bytes = try pwhash.bcrypt.strHash(password_to_use, hash_options, outbuf_slice); + return try allocator.dupe(u8, out_bytes); + }, + } + } + + pub fn verify( + allocator: std.mem.Allocator, + password: []const u8, + previous_hash: []const u8, + algorithm: ?Algorithm, + ) HashError!bool { + if (previous_hash.len == 0) { + return false; + } + + return verifyWithAlgorithm( + allocator, + password, + previous_hash, + algorithm orelse Algorithm.get(previous_hash) orelse return error.UnsupportedAlgorithm, + ); + } + + pub fn verifyWithAlgorithm( + allocator: std.mem.Allocator, + password: []const u8, + previous_hash: []const u8, + algorithm: Algorithm, + ) HashError!bool { + switch (algorithm) { + .argon2id, .argon2d, .argon2i => { + pwhash.argon2.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| { + if (err == error.PasswordVerificationFailed) { + return false; + } + + return err; + }; + return true; + }, + .bcrypt => { + pwhash.bcrypt.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| { + if (err == error.PasswordVerificationFailed) { + return false; + } + + return err; + }; + return true; + }, + } + } + }; + + pub const JSPasswordObject = struct { + const PascalToUpperUnderscoreCaseFormatter = struct { + input: []const u8, + pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + for (self.input) |c| { + if (std.ascii.isUpper(c)) { + try writer.writeByte('_'); + try writer.writeByte(c); + } else if (std.ascii.isLower(c)) { + try writer.writeByte(std.ascii.toUpper(c)); + } else { + try writer.writeByte(c); + } + } + } + }; + + pub export fn JSPasswordObject__create(globalObject: *JSC.JSGlobalObject, sync: bool) JSC.JSValue { + var object = JSValue.createEmptyObject(globalObject, 2); + object.put( + globalObject, + ZigString.static("hash"), + if (!sync) + JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash, false) + else + JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hashSync, false), + ); + object.put( + globalObject, + ZigString.static("verify"), + if (!sync) + JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify, false) + else + JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verifySync, false), + ); + return object; + } + + const HashJob = struct { + algorithm: PasswordObject.Algorithm.Value, + password: []const u8, + promise: JSC.JSPromise.Strong, + event_loop: *JSC.EventLoop, + global: *JSC.JSGlobalObject, + ref: JSC.PollRef = .{}, + task: JSC.WorkPoolTask = .{ .callback = &run }, + + pub const Result = struct { + value: Value, + ref: JSC.PollRef = .{}, + + task: JSC.AnyTask = undefined, + promise: JSC.JSPromise.Strong, + global: *JSC.JSGlobalObject, + + pub const Value = union(enum) { + err: PasswordObject.HashError, + hash: []const u8, + + pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD_{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory"); + defer bun.default_allocator.free(error_code); + const instance = globalObject.createErrorInstance("Password hashing failed with error \"{s}\"", .{@errorName(this.err)}); + instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject)); + return instance; + } + }; + + pub fn runFromJS(this: *Result) void { + var promise = this.promise; + this.promise = .{}; + this.ref.unref(this.global.bunVM()); + var global = this.global; + switch (this.value) { + .err => { + const error_instance = this.value.toErrorInstance(global); + bun.default_allocator.destroy(this); + promise.reject(global, error_instance); + }, + .hash => |value| { + const js_string = JSC.ZigString.init(value).toValueGC(global); + bun.default_allocator.destroy(this); + promise.resolve(global, js_string); + }, + } + } + }; + + pub fn deinit(this: *HashJob) void { + this.ref = .{}; + this.promise.strong.deinit(); + bun.default_allocator.free(this.password); + bun.default_allocator.destroy(this); + } + + pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value { + const value = PasswordObject.hash(bun.default_allocator, password, algorithm) catch |err| { + return Result.Value{ .err = err }; + }; + return Result.Value{ .hash = value }; + } + + pub fn run(task: *bun.ThreadPool.Task) void { + var this = @fieldParentPtr(HashJob, "task", task); + + var result = bun.default_allocator.create(Result) catch @panic("out of memory"); + result.* = Result{ + .value = getValue(this.password, this.algorithm), + .task = JSC.AnyTask.New(Result, Result.runFromJS).init(result), + .promise = this.promise, + .global = this.global, + .ref = this.ref, + }; + this.ref = .{}; + this.promise.strong = .{}; + + var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory"); + concurrent_task.* = JSC.ConcurrentTask{ + .task = JSC.Task.init(&result.task), + .auto_delete = true, + }; + this.event_loop.enqueueTaskConcurrent(concurrent_task); + this.deinit(); + } + }; + pub fn hash( + globalObject: *JSC.JSGlobalObject, + password: []const u8, + algorithm: PasswordObject.Algorithm.Value, + comptime sync: bool, + ) JSC.JSValue { + std.debug.assert(password.len > 0); // caller must check + + if (comptime sync) { + const value = HashJob.getValue(password, algorithm); + switch (value) { + .err => { + const error_instance = value.toErrorInstance(globalObject); + globalObject.throwValue(error_instance); + }, + .hash => |h| { + return JSC.ZigString.init(h).toValueGC(globalObject); + }, + } + + unreachable; + } + + var job = bun.default_allocator.create(HashJob) catch @panic("out of memory"); + var promise = JSC.JSPromise.Strong.init(globalObject); + + job.* = HashJob{ + .algorithm = algorithm, + .password = password, + .promise = promise, + .event_loop = globalObject.bunVM().eventLoop(), + .global = globalObject, + }; + + job.ref.ref(globalObject.bunVM()); + JSC.WorkPool.schedule(&job.task); + + return promise.value(); + } + + pub fn verify( + globalObject: *JSC.JSGlobalObject, + password: []const u8, + prev_hash: []const u8, + algorithm: ?PasswordObject.Algorithm, + comptime sync: bool, + ) JSC.JSValue { + std.debug.assert(password.len > 0); // caller must check + + if (comptime sync) { + const value = VerifyJob.getValue(password, prev_hash, algorithm); + switch (value) { + .err => { + const error_instance = value.toErrorInstance(globalObject); + globalObject.throwValue(error_instance); + return JSC.JSValue.undefined; + }, + .pass => |pass| { + return JSC.JSValue.jsBoolean(pass); + }, + } + + unreachable; + } + + var job = bun.default_allocator.create(VerifyJob) catch @panic("out of memory"); + var promise = JSC.JSPromise.Strong.init(globalObject); + + job.* = VerifyJob{ + .algorithm = algorithm, + .password = password, + .prev_hash = prev_hash, + .promise = promise, + .event_loop = globalObject.bunVM().eventLoop(), + .global = globalObject, + }; + + job.ref.ref(globalObject.bunVM()); + JSC.WorkPool.schedule(&job.task); + + return promise.value(); + } + + // Once we have bindings generator, this should be replaced with a generated function + pub export fn JSPasswordObject__hash( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments_ = callframe.arguments(2); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 1) { + globalObject.throwNotEnoughArguments("hash", 1, 0); + return JSC.JSValue.undefined; + } + + var algorithm = PasswordObject.Algorithm.Value.default; + + if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) { + algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse + return JSC.JSValue.undefined; + } + + var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + if (string_or_buffer.slice().len == 0) { + globalObject.throwInvalidArguments("password must not be empty", .{}); + string_or_buffer.deinit(); + return JSC.JSValue.undefined; + } + + string_or_buffer.ensureCloned(bun.default_allocator) catch { + globalObject.throwOutOfMemory(); + return JSC.JSValue.undefined; + }; + + return hash(globalObject, string_or_buffer.slice(), algorithm, false); + } + + // Once we have bindings generator, this should be replaced with a generated function + pub export fn JSPasswordObject__hashSync( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments_ = callframe.arguments(2); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 1) { + globalObject.throwNotEnoughArguments("hash", 1, 0); + return JSC.JSValue.undefined; + } + + var algorithm = PasswordObject.Algorithm.Value.default; + + if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) { + algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse + return JSC.JSValue.undefined; + } + + var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + if (string_or_buffer.slice().len == 0) { + globalObject.throwInvalidArguments("password must not be empty", .{}); + string_or_buffer.deinit(); + return JSC.JSValue.undefined; + } + + string_or_buffer.ensureCloned(bun.default_allocator) catch { + globalObject.throwOutOfMemory(); + return JSC.JSValue.undefined; + }; + defer string_or_buffer.deinit(); + + return hash(globalObject, string_or_buffer.slice(), algorithm, true); + } + + const VerifyJob = struct { + algorithm: ?PasswordObject.Algorithm = null, + password: []const u8, + prev_hash: []const u8, + promise: JSC.JSPromise.Strong, + event_loop: *JSC.EventLoop, + global: *JSC.JSGlobalObject, + ref: JSC.PollRef = .{}, + task: JSC.WorkPoolTask = .{ .callback = &run }, + + pub const Result = struct { + value: Value, + ref: JSC.PollRef = .{}, + + task: JSC.AnyTask = undefined, + promise: JSC.JSPromise.Strong, + global: *JSC.JSGlobalObject, + + pub const Value = union(enum) { + err: PasswordObject.HashError, + pass: bool, + + pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue { + var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory"); + defer bun.default_allocator.free(error_code); + const instance = globalObject.createErrorInstance("Password verification failed with error \"{s}\"", .{@errorName(this.err)}); + instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject)); + return instance; + } + }; + + pub fn runFromJS(this: *Result) void { + var promise = this.promise; + this.promise = .{}; + this.ref.unref(this.global.bunVM()); + var global = this.global; + switch (this.value) { + .err => { + const error_instance = this.value.toErrorInstance(global); + bun.default_allocator.destroy(this); + promise.reject(global, error_instance); + }, + .pass => |pass| { + bun.default_allocator.destroy(this); + promise.resolve(global, JSC.JSValue.jsBoolean(pass)); + }, + } + } + }; + + pub fn deinit(this: *VerifyJob) void { + this.ref = .{}; + this.promise.strong.deinit(); + bun.default_allocator.free(this.password); + bun.default_allocator.free(this.prev_hash); + bun.default_allocator.destroy(this); + } + + pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value { + const pass = PasswordObject.verify(bun.default_allocator, password, prev_hash, algorithm) catch |err| { + return Result.Value{ .err = err }; + }; + return Result.Value{ .pass = pass }; + } + + pub fn run(task: *bun.ThreadPool.Task) void { + var this = @fieldParentPtr(VerifyJob, "task", task); + + var result = bun.default_allocator.create(Result) catch @panic("out of memory"); + result.* = Result{ + .value = getValue(this.password, this.prev_hash, this.algorithm), + .task = JSC.AnyTask.New(Result, Result.runFromJS).init(result), + .promise = this.promise, + .global = this.global, + .ref = this.ref, + }; + this.ref = .{}; + this.promise.strong = .{}; + + var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory"); + concurrent_task.* = JSC.ConcurrentTask{ + .task = JSC.Task.init(&result.task), + .auto_delete = true, + }; + this.event_loop.enqueueTaskConcurrent(concurrent_task); + this.deinit(); + } + }; + + // Once we have bindings generator, this should be replaced with a generated function + pub export fn JSPasswordObject__verify( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments_ = callframe.arguments(3); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 2) { + globalObject.throwNotEnoughArguments("verify", 2, 0); + return JSC.JSValue.undefined; + } + + var algorithm: ?PasswordObject.Algorithm = null; + + if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) { + if (!arguments[2].isString()) { + globalObject.throwInvalidArgumentType("verify", "algorithm", "string"); + return JSC.JSValue.undefined; + } + + const algorithm_string = arguments[2].getZigString(globalObject); + + algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse { + globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message); + return JSC.JSValue.undefined; + }; + } + + var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse { + password.deinit(); + globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + if (hash_.slice().len == 0) { + password.deinit(); + return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false)); + } + + if (password.slice().len == 0) { + hash_.deinit(); + return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false)); + } + + password.ensureCloned(bun.default_allocator) catch { + hash_.deinit(); + globalObject.throwOutOfMemory(); + return JSC.JSValue.undefined; + }; + + hash_.ensureCloned(bun.default_allocator) catch { + password.deinit(); + globalObject.throwOutOfMemory(); + return JSC.JSValue.undefined; + }; + + return verify(globalObject, password.slice(), hash_.slice(), algorithm, false); + } + + // Once we have bindings generator, this should be replaced with a generated function + pub export fn JSPasswordObject__verifySync( + globalObject: *JSC.JSGlobalObject, + callframe: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + const arguments_ = callframe.arguments(3); + const arguments = arguments_.ptr[0..arguments_.len]; + + if (arguments.len < 2) { + globalObject.throwNotEnoughArguments("verify", 2, 0); + return JSC.JSValue.undefined; + } + + var algorithm: ?PasswordObject.Algorithm = null; + + if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) { + if (!arguments[2].isString()) { + globalObject.throwInvalidArgumentType("verify", "algorithm", "string"); + return JSC.JSValue.undefined; + } + + const algorithm_string = arguments[2].getZigString(globalObject); + + algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse { + globalObject.throwInvalidArgumentType("verify", "algorithm", unknwon_password_algorithm_message); + return JSC.JSValue.undefined; + }; + } + + var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse { + globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse { + password.deinit(); + globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray"); + return JSC.JSValue.undefined; + }; + + defer password.deinit(); + defer hash_.deinit(); + + if (hash_.slice().len == 0) { + return JSC.JSValue.jsBoolean(false); + } + + if (password.slice().len == 0) { + return JSC.JSValue.jsBoolean(false); + } + + return verify(globalObject, password.slice(), hash_.slice(), algorithm, true); + } + }; + pub const CryptoHasher = struct { evp: EVP = undefined, @@ -4306,3 +5103,9 @@ pub const JSZlib = struct { }; pub usingnamespace @import("./bun/subprocess.zig"); + +comptime { + if (!JSC.is_bindgen) { + _ = Crypto.JSPasswordObject.JSPasswordObject__create; + } +} |