taqlyn 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/taqlyn/client.rb +66 -0
- data/lib/taqlyn/signer.rb +47 -0
- data/lib/taqlyn.rb +6 -0
- metadata +45 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6d583ab227c70a787a0c7df284bb5af6ee135640c1a19661e609621a7c2bb7aa
|
|
4
|
+
data.tar.gz: 8e46da06d29fd8c6e76593be32376d9261ecd555d85083fadcbc2c51a996277a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c6ba2debea8fc0f2e2ed9047eb217738a4fb9e683a10e5c679d7c532c0672d6dcf8d9609cb21a313ce8f745eb94f92edd8fdea0bd683da9f7578b67d2ef90cb8
|
|
7
|
+
data.tar.gz: f12edc7c7289f49b28a83825a29e2e9c148be74092ff19323d5beb72edd7121f6c442bd003bb2e9b94c84991f3f91a534cb427d7281ff5aee7d9c5543bcc5f5b
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
require_relative "signer"
|
|
7
|
+
|
|
8
|
+
module Taqlyn
|
|
9
|
+
class ApiError < StandardError
|
|
10
|
+
attr_reader :status, :body
|
|
11
|
+
|
|
12
|
+
def initialize(status, body)
|
|
13
|
+
@status = status
|
|
14
|
+
@body = body
|
|
15
|
+
super("Taqlyn API #{status}")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Client
|
|
20
|
+
DEFAULT_API_BASE_URL = "https://api.taqlyn.com"
|
|
21
|
+
SHORT_LINKS_PATH = "/v1/short-links"
|
|
22
|
+
|
|
23
|
+
def initialize(client_id:, private_key:, base_url: nil, now: nil)
|
|
24
|
+
raw_url = (base_url && !base_url.to_s.strip.empty?) ? base_url : (ENV["TAQLYN_BASE_URL"] || ENV["TAQLYN_API_URL"] || DEFAULT_API_BASE_URL)
|
|
25
|
+
raise ArgumentError, "base_url is required" if raw_url.to_s.strip.empty?
|
|
26
|
+
raise ArgumentError, "client_id is required" if client_id.to_s.strip.empty?
|
|
27
|
+
unless client_id.start_with?("app_test_", "app_live_")
|
|
28
|
+
raise ArgumentError, "client_id must start with app_test_ or app_live_"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@base_url = raw_url.to_s.sub(%r{/+\z}, "")
|
|
32
|
+
@client_id = client_id.strip
|
|
33
|
+
@key = Signer.load_private_key(private_key)
|
|
34
|
+
@now = now || -> { Time.now.to_i }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_short_link(destination_web:, mode: nil, destination_path: nil)
|
|
38
|
+
raise ArgumentError, "destination_web is required" if destination_web.to_s.strip.empty?
|
|
39
|
+
|
|
40
|
+
body = { "destinationWeb" => destination_web.strip }
|
|
41
|
+
body["mode"] = mode if mode
|
|
42
|
+
body["destinationPath"] = destination_path if destination_path
|
|
43
|
+
request("POST", SHORT_LINKS_PATH, body)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def request(method, path, payload)
|
|
49
|
+
json = JSON.generate(payload)
|
|
50
|
+
uri = URI.parse(@base_url + path)
|
|
51
|
+
headers = Signer.headers(@key, @client_id, method, path, json, @now.call)
|
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
53
|
+
http.use_ssl = uri.scheme == "https"
|
|
54
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
|
55
|
+
req.body = json
|
|
56
|
+
req["Accept"] = "application/json"
|
|
57
|
+
req["Content-Type"] = "application/json"
|
|
58
|
+
headers.each { |k, v| req[k] = v }
|
|
59
|
+
res = http.request(req)
|
|
60
|
+
unless res.is_a?(Net::HTTPSuccess)
|
|
61
|
+
raise ApiError.new(res.code.to_i, res.body)
|
|
62
|
+
end
|
|
63
|
+
JSON.parse(res.body)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module Taqlyn
|
|
8
|
+
module Signer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def normalize_pem(pem)
|
|
12
|
+
value = pem.to_s.strip
|
|
13
|
+
value = value.gsub('\n', "\n") if value.include?('\n') && !value.include?("\n")
|
|
14
|
+
value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def canonical_message(method, path, unix_timestamp, client_id, body)
|
|
18
|
+
body_bytes = body.is_a?(String) ? body.b : body.to_s.b
|
|
19
|
+
[
|
|
20
|
+
"taqlyn-v1",
|
|
21
|
+
method.to_s.upcase,
|
|
22
|
+
path,
|
|
23
|
+
unix_timestamp.to_s,
|
|
24
|
+
client_id,
|
|
25
|
+
Digest::SHA256.hexdigest(body_bytes)
|
|
26
|
+
].join("\n")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def load_private_key(pem)
|
|
30
|
+
normalized = normalize_pem(pem)
|
|
31
|
+
raise ArgumentError, "sk_* is a UX handle only; pass the PKCS#8 PEM" if normalized.start_with?("sk_")
|
|
32
|
+
raise ArgumentError, "Private key must be a PKCS#8 PEM" unless normalized.include?("BEGIN")
|
|
33
|
+
|
|
34
|
+
OpenSSL::PKey.read(normalized)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def headers(pkey, client_id, method, path, body, unix_timestamp)
|
|
38
|
+
message = canonical_message(method, path, unix_timestamp, client_id, body)
|
|
39
|
+
signature = pkey.sign(nil, message)
|
|
40
|
+
{
|
|
41
|
+
"X-Taqlyn-Client-Id" => client_id,
|
|
42
|
+
"X-Taqlyn-Timestamp" => unix_timestamp.to_s,
|
|
43
|
+
"X-Taqlyn-Signature" => Base64.strict_encode64(signature)
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/taqlyn.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: taqlyn
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Taqlyn
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- lib/taqlyn.rb
|
|
20
|
+
- lib/taqlyn/client.rb
|
|
21
|
+
- lib/taqlyn/signer.rb
|
|
22
|
+
homepage:
|
|
23
|
+
licenses:
|
|
24
|
+
- MIT
|
|
25
|
+
metadata: {}
|
|
26
|
+
post_install_message:
|
|
27
|
+
rdoc_options: []
|
|
28
|
+
require_paths:
|
|
29
|
+
- lib
|
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - ">="
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '3.1'
|
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
requirements: []
|
|
41
|
+
rubygems_version: 3.5.22
|
|
42
|
+
signing_key:
|
|
43
|
+
specification_version: 4
|
|
44
|
+
summary: Taqlyn server SDK — Ed25519-signed short-link API
|
|
45
|
+
test_files: []
|