This document is an example of how to bind MAuth Core in MAuth-Client ruby.
The following Ruby gems are used to bind:
Add the following gems to mauth-client.gemspec:
spec.add_dependency "rake-compiler"
spec.add_dependency "rb_sys"
And update Rakefile to add the following extension task:
require "rake/extensiontask"
task build: :compile
Rake::ExtensionTask.new("mauth_core_binder") do |ext|
ext.lib_dir = "lib/mauth"
ext.source_pattern = "*.{rs,toml}"
end
task default: %i[compile spec]
Prepare an extension directory and create a new Cargo package to bind mauth-core:
mkdir -p ext/mauth_core_binder
cd ext/mauth_core_binder
cargo init . --lib
cargo add rb-sys rb-allocator
cargo add magnus --features rb-sys-interop
Set the crate-type attribute to cdylib
in Cargo.toml:
[lib]
crate-type = ["cdylib"]
Add ext/mauth_core_binder/extconf.rb
file:
require "mkmf"
require "rb_sys/mkmf"
create_rust_makefile("mauth_core_binder/mauth_core_binder")
At this point, you are ready to compile the rust extension by calling:
bundle exec rake compile
Add the mauth-core
crate to Cargo.toml:
[dependencies]
mauth-core = "0.4"
Then, add some Rust code to the lib.rs
file to call mauth-core:
use magnus::{define_class, exception, function, method, prelude::*, Error};
use rb_allocator::ruby_global_allocator;
use mauth_core::signer::Signer;
use mauth_core::verifier::Verifier;
ruby_global_allocator!();
#[magnus::wrap(class = "MAuthCore")]
struct MAuthCore {
signer: Signer,
}
impl MAuthCore {
fn new(app_uuid: String, private_key_data: String) -> Self {
let signer =
Signer::new(app_uuid, private_key_data).expect("Failed to initialize MAuthCore");
Self { signer }
}
fn sign_string(
&self,
version: u8,
verb: String,
path: String,
query: String,
body: magnus::Value,
timestamp: String,
) -> Result<String, magnus::Error> {
let body = magnus::RString::from_value(body).ok_or_else(|| Error::new(
exception::standard_error(),
"expected string",
))?;
let body_as_slice;
unsafe {
body_as_slice = body.as_slice();
}
self.signer
.sign_string(version, verb, path, query, body_as_slice, timestamp)
.map_err(|err| {
Error::new(
exception::standard_error(),
format!("Failed to generate sigunatures: {:?}", err),
)
})
}
fn verify_signature(
&self,
app_uuid: String,
public_key_data: String,
version: u8,
verb: String,
path: String,
query: String,
body: magnus::Value,
timestamp: String,
signature: String,
) -> Result<bool, magnus::Error> {
let body = magnus::RString::from_value(body).ok_or_else(|| Error::new(
exception::standard_error(),
"expected string",
))?;
let body_as_slice;
unsafe {
body_as_slice = body.as_slice();
}
match Verifier::new(app_uuid, public_key_data) {
Ok(verifier) => verifier
.verify_signature(version, verb, path, query, body, timestamp, signature)
.map_err(|err| {
Error::new(
exception::standard_error(),
format!("Failed to verify sigunatures: {:?}", err),
)
}),
Err(err) => Err(Error::new(
exception::standard_error(),
format!("Failed to initialize verifier: {:?}", err),
)),
}
}
}
#[magnus::init]
fn init() -> Result<(), Error> {
let class = define_class("MAuthCore", Default::default())?;
class.define_singleton_method("new", function!(MAuthCore::new, 2))?;
class.define_method("sign_string", method!(MAuthCore::sign_string, 6))?;
class.define_method("verify_signature", method!(MAuthCore::verify_signature, 9))?;
Ok(())
}
By adding the #[magnus::wrap(class = "MAuthCore")]
annotation, the MAuthCore struct is wrapped in a Ruby object and it is callable from Ruby.
Using the #[magnus::init]
attribute to mark the init function so it can be correctly exposed to Ruby.
Now you can call mauth-core
from Ruby code by doing this:
mauth_core = MAuthCore.new(app_uuid, public_key_data)
mauth_core.sign_string(mauth_version, verb, path, query, body, timestamp)