leash-sdk 0.2.1 → 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/leash-sdk.gemspec +1 -1
- data/lib/leash/auth.rb +141 -0
- data/lib/leash.rb +2 -1
- metadata +17 -2
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/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,7 +1,7 @@
|
|
|
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
|
|
@@ -9,7 +9,21 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2026-04-19 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
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
|