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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1c51a2f51cd93a5950ac251adab5eeb1b1787175732dd4e4804574175cf699e
4
- data.tar.gz: 9fc45a6f2808e76f84b1f82c38891c1085865cdd97f6ad31663d8ef9683f923a
3
+ metadata.gz: 43e34f3d6e756f73f7612d2386e8e8db0b8db9ebcd02497e840617d5136ee83c
4
+ data.tar.gz: b8065f2e37c1218b01c0dd88ef363694762359338167d9e2defa232976efe2d9
5
5
  SHA512:
6
- metadata.gz: 972c62c6db4c16b31b64fcfe7b8e2d60e27fb708e306801f3d895351c5f7f75c09a83c6a2a5b7a89bbe886cf41c5136b031a8b27f2509287198c004d572d6d95
7
- data.tar.gz: 9802de6dea20a65d7cd196fc50b4ebc67ebbd489fa57c53ae19a0cc6d4ffb928faa62e34c2560403f7f0746aba24f68747111d6afa24aad0887b1601b95d26ea
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 the [Leash](https://leash.build) platform integrations API. Access Gmail, Google Calendar, Google Drive, and more through the Leash platform proxy.
3
+ Ruby SDK for Leash-hosted integrations.
4
4
 
5
- ## Installation
5
+ Use it to call Gmail, Google Calendar, Google Drive, and custom provider actions through the Leash platform proxy.
6
6
 
7
- Add to your Gemfile:
7
+ ## Installation
8
8
 
9
9
  ```ruby
10
10
  gem "leash-sdk"
11
11
  ```
12
12
 
13
- Or install directly:
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(auth_token: ENV["LEASH_AUTH_TOKEN"])
25
-
26
- # Gmail
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
- # Google Drive
47
- files = client.drive.list_files
48
- file = client.drive.get_file("file_id_123")
49
- results = client.drive.search_files("quarterly report", max_results: 5)
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
- ## Configuration
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
- | Method | Description |
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
- ### Connections
41
+ ## Features
126
42
 
127
- | Method | Description |
128
- |--------|-------------|
129
- | `connected?(provider_id)` | Check if provider is connected |
130
- | `connections` | Get all connection statuses |
131
- | `connect_url(provider_id, return_url:)` | Get OAuth connect URL |
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
- ## Requirements
52
+ ## Notes
134
53
 
135
- - Ruby >= 3.0
136
- - No external dependencies (uses stdlib `net/http`, `json`, `uri`)
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
- MIT
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
- # No runtime dependencies -- stdlib only (net/http, json, uri).
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
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "leash/integrations"
4
+ require_relative "leash/auth"
4
5
 
5
6
  module Leash
6
- VERSION = "0.2.0"
7
+ VERSION = "0.3.0"
7
8
  end
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.2.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-14 00:00:00.000000000 Z
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