leash-sdk 0.2.0 → 0.3.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 +4 -4
- data/README.md +29 -109
- data/leash-sdk.gemspec +1 -1
- data/lib/leash/auth.rb +141 -0
- data/lib/leash.rb +2 -1
- metadata +18 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43e34f3d6e756f73f7612d2386e8e8db0b8db9ebcd02497e840617d5136ee83c
|
|
4
|
+
data.tar.gz: b8065f2e37c1218b01c0dd88ef363694762359338167d9e2defa232976efe2d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cfedee526258e982cf6220618965ac55d7adfcb054ea325510ef74d174765eb956cb8e2eeefcb031ce02af5d8645cf239b9de61f23995cad5ae60139d97b74b3
|
|
7
|
+
data.tar.gz: a6693f07510e34bd6f39fe4c0d3815a0266088b8cc7c8609d6075f4624be27f0d989384274efb2bf658ac60b3f42b33d2b5962f0550f6599f1787c40169e1e47
|
data/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# Leash SDK for Ruby
|
|
2
2
|
|
|
3
|
-
Ruby SDK for
|
|
3
|
+
Ruby SDK for Leash-hosted integrations.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use it to call Gmail, Google Calendar, Google Drive, and custom provider actions through the Leash platform proxy.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
gem "leash-sdk"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
or:
|
|
14
14
|
|
|
15
|
-
```
|
|
15
|
+
```bash
|
|
16
16
|
gem install leash-sdk
|
|
17
17
|
```
|
|
18
18
|
|
|
@@ -21,120 +21,40 @@ gem install leash-sdk
|
|
|
21
21
|
```ruby
|
|
22
22
|
require "leash"
|
|
23
23
|
|
|
24
|
-
client = Leash::Integrations.new(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
messages = client.gmail.list_messages(query: "is:unread", max_results: 10)
|
|
28
|
-
message = client.gmail.get_message("msg_id_123")
|
|
29
|
-
client.gmail.send_message(to: "friend@example.com", subject: "Hello", body: "Hi there!")
|
|
30
|
-
labels = client.gmail.list_labels
|
|
31
|
-
|
|
32
|
-
# Google Calendar
|
|
33
|
-
calendars = client.calendar.list_calendars
|
|
34
|
-
events = client.calendar.list_events(
|
|
35
|
-
time_min: "2026-04-10T00:00:00Z",
|
|
36
|
-
time_max: "2026-04-17T00:00:00Z",
|
|
37
|
-
single_events: true,
|
|
38
|
-
order_by: "startTime"
|
|
39
|
-
)
|
|
40
|
-
client.calendar.create_event(
|
|
41
|
-
summary: "Team standup",
|
|
42
|
-
start: { "dateTime" => "2026-04-11T09:00:00-04:00" },
|
|
43
|
-
end_time: { "dateTime" => "2026-04-11T09:30:00-04:00" }
|
|
24
|
+
client = Leash::Integrations.new(
|
|
25
|
+
auth_token: ENV["LEASH_AUTH_TOKEN"],
|
|
26
|
+
api_key: ENV["LEASH_API_KEY"]
|
|
44
27
|
)
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## Connection Management
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
# Check if a provider is connected
|
|
56
|
-
client.connected?("gmail") # => true/false
|
|
57
|
-
|
|
58
|
-
# Get all connections
|
|
59
|
-
client.connections # => [{ "providerId" => "gmail", "status" => "active", ... }]
|
|
60
|
-
|
|
61
|
-
# Get OAuth connect URL (for UI buttons)
|
|
62
|
-
url = client.connect_url("gmail", return_url: "https://myapp.com/settings")
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Error Handling
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
begin
|
|
69
|
-
client.gmail.list_messages
|
|
70
|
-
rescue Leash::NotConnectedError => e
|
|
71
|
-
# Redirect user to connect: e.connect_url
|
|
72
|
-
puts "Please connect Gmail: #{e.connect_url}"
|
|
73
|
-
rescue Leash::TokenExpiredError => e
|
|
74
|
-
# Token needs refresh: e.connect_url
|
|
75
|
-
puts "Token expired, reconnect: #{e.connect_url}"
|
|
76
|
-
rescue Leash::Error => e
|
|
77
|
-
# General API error
|
|
78
|
-
puts "Error (#{e.code}): #{e.message}"
|
|
29
|
+
if client.connected?("gmail")
|
|
30
|
+
messages = client.gmail.list_messages(max_results: 5)
|
|
31
|
+
puts messages
|
|
32
|
+
else
|
|
33
|
+
puts client.connect_url("gmail", return_url: "https://myapp.example.com/settings")
|
|
79
34
|
end
|
|
80
35
|
```
|
|
81
36
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
# Custom platform URL
|
|
86
|
-
client = Leash::Integrations.new(
|
|
87
|
-
auth_token: "your-token",
|
|
88
|
-
platform_url: "https://your-instance.leash.build"
|
|
89
|
-
)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## API Reference
|
|
93
|
-
|
|
94
|
-
### `Leash::Integrations.new(auth_token:, platform_url: "https://leash.build")`
|
|
95
|
-
|
|
96
|
-
Creates a new client instance.
|
|
97
|
-
|
|
98
|
-
### Gmail (`client.gmail`)
|
|
99
|
-
|
|
100
|
-
| Method | Description |
|
|
101
|
-
|--------|-------------|
|
|
102
|
-
| `list_messages(query:, max_results:, label_ids:, page_token:)` | List messages |
|
|
103
|
-
| `get_message(message_id, format:)` | Get a message by ID |
|
|
104
|
-
| `send_message(to:, subject:, body:, cc:, bcc:)` | Send an email |
|
|
105
|
-
| `search_messages(query, max_results:)` | Search messages |
|
|
106
|
-
| `list_labels` | List all labels |
|
|
107
|
-
|
|
108
|
-
### Calendar (`client.calendar`)
|
|
109
|
-
|
|
110
|
-
| Method | Description |
|
|
111
|
-
|--------|-------------|
|
|
112
|
-
| `list_calendars` | List all calendars |
|
|
113
|
-
| `list_events(calendar_id:, time_min:, time_max:, max_results:, single_events:, order_by:)` | List events |
|
|
114
|
-
| `create_event(summary:, start:, end_time:, calendar_id:, description:, location:, attendees:)` | Create an event |
|
|
115
|
-
| `get_event(event_id, calendar_id:)` | Get an event by ID |
|
|
116
|
-
|
|
117
|
-
### Drive (`client.drive`)
|
|
37
|
+
## Default Platform URL
|
|
118
38
|
|
|
119
|
-
|
|
120
|
-
|--------|-------------|
|
|
121
|
-
| `list_files(query:, max_results:, folder_id:)` | List files |
|
|
122
|
-
| `get_file(file_id)` | Get file metadata |
|
|
123
|
-
| `search_files(query, max_results:)` | Search files |
|
|
39
|
+
- `https://leash.build`
|
|
124
40
|
|
|
125
|
-
|
|
41
|
+
## Features
|
|
126
42
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
43
|
+
- Gmail
|
|
44
|
+
- Google Calendar
|
|
45
|
+
- Google Drive
|
|
46
|
+
- connection status lookup
|
|
47
|
+
- connect URL generation
|
|
48
|
+
- generic provider calls
|
|
49
|
+
- custom integration calls
|
|
50
|
+
- app env fetch and caching
|
|
132
51
|
|
|
133
|
-
##
|
|
52
|
+
## Notes
|
|
134
53
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
54
|
+
- `auth_token` should be a valid Leash platform JWT
|
|
55
|
+
- `api_key` is optional, but useful for app-scoped access
|
|
56
|
+
- OAuth token handling remains a platform concern
|
|
137
57
|
|
|
138
58
|
## License
|
|
139
59
|
|
|
140
|
-
|
|
60
|
+
Apache-2.0
|
data/leash-sdk.gemspec
CHANGED
|
@@ -21,5 +21,5 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.files = Dir["lib/**/*.rb"] + ["leash-sdk.gemspec", "Gemfile", "README.md", "LICENSE"]
|
|
22
22
|
spec.require_paths = ["lib"]
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
spec.add_dependency "jwt", ">= 2.7"
|
|
25
25
|
end
|
data/lib/leash/auth.rb
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jwt"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Leash
|
|
7
|
+
# Raised when authentication fails (missing cookie, invalid/expired token, etc.)
|
|
8
|
+
class AuthError < Error
|
|
9
|
+
def initialize(message = "Authentication failed")
|
|
10
|
+
super(message, code: "auth_error")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Simple value object representing an authenticated Leash user.
|
|
15
|
+
class User
|
|
16
|
+
attr_reader :id, :email, :name, :picture
|
|
17
|
+
|
|
18
|
+
# @param id [String]
|
|
19
|
+
# @param email [String]
|
|
20
|
+
# @param name [String, nil]
|
|
21
|
+
# @param picture [String, nil]
|
|
22
|
+
def initialize(id:, email:, name: nil, picture: nil)
|
|
23
|
+
@id = id
|
|
24
|
+
@email = email
|
|
25
|
+
@name = name
|
|
26
|
+
@picture = picture
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ==(other)
|
|
30
|
+
other.is_a?(User) &&
|
|
31
|
+
id == other.id &&
|
|
32
|
+
email == other.email &&
|
|
33
|
+
name == other.name &&
|
|
34
|
+
picture == other.picture
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Framework-agnostic server auth helper.
|
|
39
|
+
#
|
|
40
|
+
# Works with any request object that exposes either:
|
|
41
|
+
# - request.cookies (Hash) — Rack / Rails / Sinatra
|
|
42
|
+
# - request.env['HTTP_COOKIE'] or request.get_header('HTTP_COOKIE') — raw Rack env
|
|
43
|
+
#
|
|
44
|
+
# Does NOT require rails, sinatra, or rack.
|
|
45
|
+
module Auth
|
|
46
|
+
COOKIE_NAME = "leash-auth"
|
|
47
|
+
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
# Read the leash-auth JWT from the request, decode it, and return a {Leash::User}.
|
|
51
|
+
#
|
|
52
|
+
# @param request [#cookies, #env, #get_header] any Rack-like request object
|
|
53
|
+
# @return [Leash::User]
|
|
54
|
+
# @raise [Leash::AuthError] when the cookie is missing or the token is invalid/expired
|
|
55
|
+
def get_user(request)
|
|
56
|
+
token = extract_token(request)
|
|
57
|
+
raise AuthError, "Missing leash-auth cookie" if token.nil? || token.empty?
|
|
58
|
+
|
|
59
|
+
payload = decode_token(token)
|
|
60
|
+
build_user(payload)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check whether the request carries a valid leash-auth cookie.
|
|
64
|
+
#
|
|
65
|
+
# @param request [#cookies, #env, #get_header]
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def authenticated?(request)
|
|
68
|
+
get_user(request)
|
|
69
|
+
true
|
|
70
|
+
rescue AuthError
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @api private
|
|
75
|
+
def extract_token(request)
|
|
76
|
+
# Strategy 1: request.cookies hash (Rack / Rails / Sinatra)
|
|
77
|
+
if request.respond_to?(:cookies)
|
|
78
|
+
cookies = request.cookies
|
|
79
|
+
if cookies.is_a?(Hash)
|
|
80
|
+
value = cookies[COOKIE_NAME] || cookies[COOKIE_NAME.to_sym]
|
|
81
|
+
return value if value
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Strategy 2: raw Cookie header from env or get_header
|
|
86
|
+
raw = nil
|
|
87
|
+
if request.respond_to?(:env) && request.env.is_a?(Hash)
|
|
88
|
+
raw = request.env["HTTP_COOKIE"]
|
|
89
|
+
end
|
|
90
|
+
if raw.nil? && request.respond_to?(:get_header)
|
|
91
|
+
begin
|
|
92
|
+
raw = request.get_header("HTTP_COOKIE")
|
|
93
|
+
rescue StandardError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
parse_cookie_header(raw) if raw
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @api private
|
|
102
|
+
def parse_cookie_header(header)
|
|
103
|
+
return nil if header.nil?
|
|
104
|
+
|
|
105
|
+
header.split(";").each do |pair|
|
|
106
|
+
key, value = pair.strip.split("=", 2)
|
|
107
|
+
return value if key == COOKIE_NAME
|
|
108
|
+
end
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
def decode_token(token)
|
|
114
|
+
secret = ENV["LEASH_JWT_SECRET"]
|
|
115
|
+
if secret && !secret.empty?
|
|
116
|
+
decoded = JWT.decode(token, secret, true, algorithms: ["HS256"])
|
|
117
|
+
else
|
|
118
|
+
decoded = JWT.decode(token, nil, false)
|
|
119
|
+
end
|
|
120
|
+
decoded.first
|
|
121
|
+
rescue JWT::ExpiredSignature
|
|
122
|
+
raise AuthError, "Token has expired"
|
|
123
|
+
rescue JWT::DecodeError => e
|
|
124
|
+
raise AuthError, "Invalid token: #{e.message}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @api private
|
|
128
|
+
def build_user(payload)
|
|
129
|
+
id = payload["id"] || payload["sub"]
|
|
130
|
+
email = payload["email"]
|
|
131
|
+
raise AuthError, "Token payload missing required fields (id/sub, email)" unless id && email
|
|
132
|
+
|
|
133
|
+
User.new(
|
|
134
|
+
id: id,
|
|
135
|
+
email: email,
|
|
136
|
+
name: payload["name"],
|
|
137
|
+
picture: payload["picture"]
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/leash.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leash-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leash
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2026-04-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jwt
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.7'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.7'
|
|
13
27
|
description: Access Gmail, Google Calendar, Google Drive, and more through the Leash
|
|
14
28
|
platform proxy. No API keys needed -- uses your Leash auth token.
|
|
15
29
|
email:
|
|
@@ -23,6 +37,7 @@ files:
|
|
|
23
37
|
- README.md
|
|
24
38
|
- leash-sdk.gemspec
|
|
25
39
|
- lib/leash.rb
|
|
40
|
+
- lib/leash/auth.rb
|
|
26
41
|
- lib/leash/calendar.rb
|
|
27
42
|
- lib/leash/custom_integration.rb
|
|
28
43
|
- lib/leash/drive.rb
|