autentique 0.1.0 → 0.1.1
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/CHANGELOG.md +40 -50
- data/README.md +67 -402
- data/lib/autentique/errors.rb +6 -0
- data/lib/autentique/resources/documents/pending.rb +3 -1
- data/lib/autentique/resources/documents/reject.rb +25 -0
- data/lib/autentique/resources/documents.rb +2 -0
- data/lib/autentique/version.rb +1 -1
- data/lib/autentique/webhook_processor.rb +83 -0
- data/lib/autentique.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddb99e141936ac88e36ddf58b662d42fea5bdf3c9efba7f49d278dc87052dab9
|
|
4
|
+
data.tar.gz: '0938f943bba5d36e839189709176567cf0e15dd17372c7952b176adc7990eb98'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4be7aa4c497b937f372ad0e4f3c3480710cad03795e4eba873fe78fb691f86031b190257a532e17d26c1c500f63ba9f5a7a11d0c62c6d6aec037c1e691499b58
|
|
7
|
+
data.tar.gz: 955ca0c626071bc29ec6d71db8070af6aa3d525d8f65ee0a59cbaccd25e0e9d0b0bee196fbb87622c0eb73b6385b761bf18fa1345116b15565bf13cc0aaa4f7c
|
data/CHANGELOG.md
CHANGED
|
@@ -5,58 +5,48 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.1.1] - 2026-04-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `WebhookProcessor` for parsing and verifying incoming Autentique webhook events,
|
|
12
|
+
including HMAC-SHA256 signature verification via the `X-Autentique-Signature` header
|
|
13
|
+
- `documents.reject` for programmatically rejecting documents with an optional reason
|
|
14
|
+
- `InvalidSignatureError` and `WebhookError` error classes
|
|
15
|
+
- Comprehensive documentation split into `docs/` — documents, folders, webhooks,
|
|
16
|
+
and configuration reference
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Webhook event types updated to Autentique's current dot-notation format
|
|
20
|
+
(`document.finished`, `signature.accepted`, etc.)
|
|
21
|
+
- Document creation now accepts file input as a path, `File`, or `StringIO` —
|
|
22
|
+
`file_name` and `mime_type` kwargs removed from the public interface
|
|
23
|
+
- README restructured to focus on quick start, with detailed reference moved to `docs/`
|
|
24
|
+
- Improved test isolation for HTTP layer using `build_http_client` stub
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Duplicate file part bug in multipart body construction
|
|
28
|
+
- `GraphQLDocumentsData` struct arity mismatch in test helpers
|
|
9
29
|
|
|
10
30
|
## [0.1.0] - 2025-01-25
|
|
11
31
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- Document resource with create, find, list, and delete operations
|
|
16
|
-
- Folder resource for folder management
|
|
17
|
-
- Model classes for Document, Signature, DocumentInput, and SignerInput
|
|
18
|
-
- Full support for document creation with:
|
|
19
|
-
- Multiple signers
|
|
20
|
-
- Various authentication methods (email, SMS, WhatsApp)
|
|
21
|
-
- Security verifications (SMS, photo ID, biometric)
|
|
22
|
-
- Signature field positioning
|
|
23
|
-
- Advanced document configurations
|
|
24
|
-
- Sandbox mode support for testing
|
|
25
|
-
- Comprehensive error handling
|
|
26
|
-
- Rate limit awareness (60 requests/minute)
|
|
27
|
-
- Rails integration examples
|
|
28
|
-
- Complete documentation and examples
|
|
29
|
-
|
|
30
|
-
### Features
|
|
31
|
-
- ✅ Create documents with file upload
|
|
32
|
-
- ✅ Retrieve documents by ID
|
|
33
|
-
- ✅ List documents with filters
|
|
34
|
-
- ✅ Delete documents
|
|
35
|
-
- ✅ Folder management (list, create, delete)
|
|
36
|
-
- ✅ Support for all signer actions (SIGN, SIGN_AS_A_WITNESS, APPROVE, RECOGNIZE)
|
|
37
|
-
- ✅ Multiple delivery methods (email, SMS, WhatsApp, link-based)
|
|
38
|
-
- ✅ Security verifications
|
|
39
|
-
- ✅ Signature field positioning
|
|
40
|
-
- ✅ Document locale support
|
|
41
|
-
- ✅ Sandbox mode
|
|
42
|
-
- ✅ Comprehensive error handling
|
|
43
|
-
|
|
44
|
-
### Dependencies
|
|
45
|
-
- graphql-client ~> 0.18
|
|
46
|
-
- mime-types ~> 3.0
|
|
47
|
-
|
|
48
|
-
### Development Dependencies
|
|
49
|
-
- rspec ~> 3.12
|
|
50
|
-
- webmock ~> 3.18
|
|
51
|
-
- vcr ~> 6.1
|
|
52
|
-
- rubocop ~> 1.50
|
|
53
|
-
|
|
54
|
-
## [0.0.1] - 2025-01-24
|
|
32
|
+
Initial release. A lightweight, framework-agnostic Ruby client for the
|
|
33
|
+
Autentique digital signature API, providing a clean idiomatic interface
|
|
34
|
+
over Autentique's GraphQL API.
|
|
55
35
|
|
|
56
36
|
### Added
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
- `Autentique::Client` with global configuration and sandbox mode support
|
|
38
|
+
- `client.documents` resource — create, find, list, pending, and delete
|
|
39
|
+
- `client.folders` resource — list, create, and delete
|
|
40
|
+
- Document creation supports multiple signers with email, SMS, WhatsApp,
|
|
41
|
+
and link-based delivery; all signer actions (SIGN, SIGN_AS_A_WITNESS,
|
|
42
|
+
APPROVE, RECOGNIZE); security verifications (SMS, photo ID, biometric,
|
|
43
|
+
SERPRO); signature field positioning; and advanced document configurations
|
|
44
|
+
- `Autentique::Models::Document`, `Signature`, `DocumentInput`, and
|
|
45
|
+
`SignerInput` model classes for type safety
|
|
46
|
+
- Structured error handling with `AuthenticationError`, `RateLimitError`,
|
|
47
|
+
`ValidationError`, `QueryError`, and `UploadError`
|
|
48
|
+
- Runtime dependencies: `graphql-client ~> 0.18`, `mime-types ~> 3.0`
|
|
49
|
+
|
|
50
|
+
## [Unreleased]: https://github.com/keithyoder/autentique-ruby/compare/v0.1.1...HEAD
|
|
51
|
+
## [0.1.1]: https://github.com/keithyoder/autentique-ruby/compare/v0.1.0...v0.1.1
|
|
52
|
+
## [0.1.0]: https://github.com/keithyoder/autentique-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -1,291 +1,83 @@
|
|
|
1
1
|
# Autentique Ruby Gem
|
|
2
2
|
|
|
3
|
-
A Ruby client for the [Autentique](https://autentique.com.br/) digital signature API. This gem provides a clean, idiomatic
|
|
3
|
+
A Ruby client for the [Autentique](https://autentique.com.br/) digital signature API. This gem provides a clean, idiomatic interface to Autentique's GraphQL API for document signing and management.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/keithyoder/autentique-ruby/actions/workflows/ci.yml)
|
|
6
6
|
[](https://github.com/keithyoder/autentique-ruby/actions/workflows/security.yml)
|
|
7
|
-
[](https://badge.fury.io/rb/autentique)
|
|
8
|
+
[](https://www.ruby-lang.org)
|
|
8
9
|
|
|
9
10
|
## Features
|
|
10
11
|
|
|
11
|
-
- 🔐 **Simple Authentication**
|
|
12
|
-
- 📄 **Document Management**
|
|
13
|
-
- ✍️ **Flexible Signing**
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- 🛡️ **Type Safety**
|
|
18
|
-
- ⚡ **Rate Limiting** - Built-in rate limit handling (60 requests/minute)
|
|
12
|
+
- 🔐 **Simple Authentication** — Easy API key configuration
|
|
13
|
+
- 📄 **Document Management** — Create, retrieve, list, and delete documents
|
|
14
|
+
- ✍️ **Flexible Signing** — Multiple signers with various authentication methods
|
|
15
|
+
- 📁 **Folder Management** — Organize documents in folders
|
|
16
|
+
- 🪝 **Webhook Processing** — Verify and process incoming Autentique webhook events
|
|
17
|
+
- 🏖️ **Sandbox Mode** — Test without consuming document credits
|
|
18
|
+
- 🛡️ **Type Safety** — Model classes for structured data
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
22
|
-
Add
|
|
22
|
+
Add to your Gemfile:
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
25
25
|
gem 'autentique'
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Then run `bundle install`, or install directly with `gem install autentique`.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
bundle install
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Or install it yourself as:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
gem install autentique
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Configuration
|
|
41
|
-
|
|
42
|
-
### Using Environment Variables
|
|
30
|
+
## Quick Start
|
|
43
31
|
|
|
44
32
|
```ruby
|
|
45
|
-
# Set your API key as an environment variable
|
|
46
|
-
ENV['AUTENTIQUE_API_KEY'] = 'your_api_key_here'
|
|
47
|
-
|
|
48
|
-
# Create a client
|
|
49
33
|
client = Autentique::Client.new(api_key: ENV['AUTENTIQUE_API_KEY'])
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Global Configuration
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
Autentique.configure do |config|
|
|
56
|
-
config.api_key = 'your_api_key_here'
|
|
57
|
-
config.sandbox = false # Set to true for testing
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Use the configured client
|
|
61
|
-
client = Autentique.client
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Direct Initialization
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
client = Autentique::Client.new(
|
|
68
|
-
api_key: 'your_api_key_here',
|
|
69
|
-
sandbox: false
|
|
70
|
-
)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Usage
|
|
74
|
-
|
|
75
|
-
### Creating Documents
|
|
76
|
-
|
|
77
|
-
#### Basic Document Creation
|
|
78
|
-
|
|
79
|
-
```ruby
|
|
80
|
-
client = Autentique::Client.new(api_key: 'your_api_key')
|
|
81
34
|
|
|
35
|
+
# Create a document
|
|
82
36
|
document = client.documents.create(
|
|
83
37
|
file: '/path/to/contract.pdf',
|
|
84
|
-
document: {
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
signers: [
|
|
88
|
-
{
|
|
89
|
-
email: 'employee@example.com',
|
|
90
|
-
action: 'SIGN'
|
|
91
|
-
}
|
|
92
|
-
]
|
|
38
|
+
document: { name: 'Employment Contract' },
|
|
39
|
+
signers: [{ email: 'employee@example.com', action: 'SIGN' }]
|
|
93
40
|
)
|
|
94
41
|
|
|
95
|
-
puts
|
|
96
|
-
puts
|
|
97
|
-
```
|
|
42
|
+
puts document.id
|
|
43
|
+
puts document.signatures.first.short_link
|
|
98
44
|
|
|
99
|
-
|
|
45
|
+
# Retrieve a document
|
|
46
|
+
doc = client.documents.find('document-uuid')
|
|
47
|
+
puts doc.signed? ? 'Signed' : 'Pending'
|
|
100
48
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
message: 'Please review and sign this contract',
|
|
107
|
-
reminder: 'WEEKLY', # Send weekly reminders
|
|
108
|
-
sortable: true, # Signers must sign in order
|
|
109
|
-
refusable: true, # Allow document rejection
|
|
110
|
-
qualified: true, # Enable qualified signatures
|
|
111
|
-
scrolling_required: true, # Require full scroll before signing
|
|
112
|
-
stop_on_rejected: true, # Stop process if rejected
|
|
113
|
-
new_signature_style: true, # Use new signature fields
|
|
114
|
-
deadline_at: '2025-12-31T23:59:59.999Z',
|
|
115
|
-
configs: {
|
|
116
|
-
notification_finished: true, # Notify when all signed
|
|
117
|
-
notification_signed: true, # Notify signer after signing
|
|
118
|
-
signature_appearance: 'DRAW' # Force signature style
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
signers: [
|
|
122
|
-
{
|
|
123
|
-
email: 'signer1@example.com',
|
|
124
|
-
action: 'SIGN',
|
|
125
|
-
configs: { cpf: '12345678900' }, # Validate CPF
|
|
126
|
-
positions: [
|
|
127
|
-
{ x: 5.0, y: 90.0, z: 1, element: 'SIGNATURE' }
|
|
128
|
-
]
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: 'Witness Name',
|
|
132
|
-
action: 'SIGN_AS_A_WITNESS',
|
|
133
|
-
positions: [
|
|
134
|
-
{ x: 75.0, y: 90.0, z: 1, element: 'NAME' }
|
|
135
|
-
]
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
phone: '+5554999999999',
|
|
139
|
-
delivery_method: 'DELIVERY_METHOD_WHATSAPP',
|
|
140
|
-
action: 'SIGN',
|
|
141
|
-
security_verifications: [
|
|
142
|
-
{ type: 'SMS', verify_phone: '+5554999999999' }
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
],
|
|
146
|
-
folder_id: 'folder-uuid' # Optional: organize in folder
|
|
49
|
+
# Process an incoming webhook
|
|
50
|
+
processor = Autentique::WebhookProcessor.new(
|
|
51
|
+
request.body.read,
|
|
52
|
+
secret: ENV['AUTENTIQUE_WEBHOOK_SECRET'],
|
|
53
|
+
signature: request.headers['X-Autentique-Signature']
|
|
147
54
|
)
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
#### Document Actions
|
|
151
|
-
|
|
152
|
-
Signers can perform different actions:
|
|
153
|
-
- `SIGN` - Sign the document
|
|
154
|
-
- `SIGN_AS_A_WITNESS` - Sign as a witness
|
|
155
|
-
- `APPROVE` - Approve the document
|
|
156
|
-
- `RECOGNIZE` - Acknowledge the document
|
|
157
|
-
|
|
158
|
-
#### Delivery Methods
|
|
159
|
-
|
|
160
|
-
For phone-based signers:
|
|
161
|
-
- `DELIVERY_METHOD_WHATSAPP` - Send via WhatsApp
|
|
162
|
-
- `DELIVERY_METHOD_SMS` - Send via SMS
|
|
163
|
-
|
|
164
|
-
#### Security Verifications
|
|
165
|
-
|
|
166
|
-
Add extra security layers:
|
|
167
|
-
|
|
168
|
-
```ruby
|
|
169
|
-
signers: [
|
|
170
|
-
{
|
|
171
|
-
email: 'signer@example.com',
|
|
172
|
-
action: 'SIGN',
|
|
173
|
-
security_verifications: [
|
|
174
|
-
{ type: 'SMS', verify_phone: '+5554999999999' }, # SMS verification
|
|
175
|
-
{ type: 'MANUAL' }, # Manual photo ID approval
|
|
176
|
-
{ type: 'UPLOAD' }, # Photo ID upload
|
|
177
|
-
{ type: 'LIVE' }, # Selfie + liveness check
|
|
178
|
-
{ type: 'PF_FACIAL' }, # SERPRO biometric
|
|
179
|
-
{ type: 'BIOMETRIC_AND_TEXT_EXTRACTION' } # Photo ID + facematch
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
]
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Retrieving Documents
|
|
186
|
-
|
|
187
|
-
```ruby
|
|
188
|
-
# Get a specific document
|
|
189
|
-
document = client.documents.find('document-uuid')
|
|
190
|
-
|
|
191
|
-
puts "Document: #{document.name}"
|
|
192
|
-
puts "Status: #{document.signed? ? 'Signed' : 'Pending'}"
|
|
193
|
-
|
|
194
|
-
document.signatures.each do |signature|
|
|
195
|
-
puts "Signer: #{signature.email}"
|
|
196
|
-
puts "Status: #{signature.signed? ? 'Signed' : 'Pending'}"
|
|
197
|
-
puts "Link: #{signature.short_link}" if signature.pending?
|
|
198
|
-
end
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Listing Documents
|
|
202
55
|
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
pending = client.documents.pending(limit: 20, page: 1)
|
|
206
|
-
|
|
207
|
-
pending.each do |doc|
|
|
208
|
-
puts "#{doc.name} - #{doc.id}"
|
|
56
|
+
processor.process do |event_type, data|
|
|
57
|
+
puts "#{event_type}: #{data['id']}"
|
|
209
58
|
end
|
|
210
|
-
|
|
211
|
-
# List all documents with filter
|
|
212
|
-
signed_docs = client.documents.list(status: 'SIGNED', limit: 50)
|
|
213
|
-
rejected_docs = client.documents.list(status: 'REJECTED')
|
|
214
|
-
all_docs = client.documents.list(limit: 100)
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Deleting Documents
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
client.documents.delete('document-uuid')
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Working with Folders
|
|
224
|
-
|
|
225
|
-
```ruby
|
|
226
|
-
# List folders
|
|
227
|
-
folders = client.folders.list
|
|
228
|
-
folders.each { |f| puts "#{f['name']} - #{f['id']}" }
|
|
229
|
-
|
|
230
|
-
# Create a folder
|
|
231
|
-
folder = client.folders.create(name: 'Contracts 2025')
|
|
232
|
-
puts "Created folder: #{folder['id']}"
|
|
233
|
-
|
|
234
|
-
# Delete a folder
|
|
235
|
-
client.folders.delete(id: 'folder-uuid')
|
|
236
59
|
```
|
|
237
60
|
|
|
238
|
-
|
|
61
|
+
## Configuration
|
|
239
62
|
|
|
240
|
-
|
|
63
|
+
### Environment variable
|
|
241
64
|
|
|
242
65
|
```ruby
|
|
243
|
-
|
|
244
|
-
client = Autentique::Client.new(
|
|
245
|
-
api_key: 'your_api_key',
|
|
246
|
-
sandbox: true
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
# Or per request
|
|
250
|
-
document = client.documents.create(
|
|
251
|
-
file: '/path/to/test.pdf',
|
|
252
|
-
document: { name: 'Test Doc' },
|
|
253
|
-
signers: [{ email: 'test@example.com', action: 'SIGN' }],
|
|
254
|
-
sandbox: true
|
|
255
|
-
)
|
|
66
|
+
client = Autentique::Client.new(api_key: ENV['AUTENTIQUE_API_KEY'])
|
|
256
67
|
```
|
|
257
68
|
|
|
258
|
-
###
|
|
259
|
-
|
|
260
|
-
For better type safety and IDE support:
|
|
69
|
+
### Global configuration (recommended for Rails)
|
|
261
70
|
|
|
262
71
|
```ruby
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
refusable: true
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
# Use SignerInput model
|
|
271
|
-
signer = Autentique::Models::SignerInput.new(
|
|
272
|
-
email: 'signer@example.com',
|
|
273
|
-
action: 'SIGN',
|
|
274
|
-
positions: [
|
|
275
|
-
{ x: 10.0, y: 90.0, z: 1, element: 'SIGNATURE' }
|
|
276
|
-
]
|
|
277
|
-
)
|
|
72
|
+
Autentique.configure do |config|
|
|
73
|
+
config.api_key = Rails.application.credentials.dig(:autentique, :api_key)
|
|
74
|
+
config.sandbox = Rails.env.development? || Rails.env.test?
|
|
75
|
+
end
|
|
278
76
|
|
|
279
|
-
|
|
280
|
-
file: 'contract.pdf',
|
|
281
|
-
document: doc_input,
|
|
282
|
-
signers: [signer]
|
|
283
|
-
)
|
|
77
|
+
client = Autentique.client
|
|
284
78
|
```
|
|
285
79
|
|
|
286
|
-
### Rails
|
|
287
|
-
|
|
288
|
-
#### Initializer
|
|
80
|
+
### Rails initializer
|
|
289
81
|
|
|
290
82
|
Create `config/initializers/autentique.rb`:
|
|
291
83
|
|
|
@@ -296,209 +88,82 @@ Autentique.configure do |config|
|
|
|
296
88
|
end
|
|
297
89
|
```
|
|
298
90
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
```ruby
|
|
302
|
-
class Contrato < ApplicationRecord
|
|
303
|
-
belongs_to :pessoa
|
|
304
|
-
|
|
305
|
-
def enviar_para_assinatura
|
|
306
|
-
client = Autentique.client
|
|
307
|
-
|
|
308
|
-
documento = client.documents.create(
|
|
309
|
-
file: gerar_pdf,
|
|
310
|
-
document: {
|
|
311
|
-
name: "Contrato #{id}",
|
|
312
|
-
message: 'Por favor, assine este contrato'
|
|
313
|
-
},
|
|
314
|
-
signers: [
|
|
315
|
-
{
|
|
316
|
-
email: pessoa.email,
|
|
317
|
-
action: 'SIGN',
|
|
318
|
-
configs: { cpf: pessoa.cpf }
|
|
319
|
-
}
|
|
320
|
-
]
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
update(
|
|
324
|
-
documentos: (documentos || []) << {
|
|
325
|
-
'id' => documento.id,
|
|
326
|
-
'nome' => documento.name,
|
|
327
|
-
'data' => documento.created_at
|
|
328
|
-
}
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
documento
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def verificar_assinatura(documento_id)
|
|
335
|
-
client = Autentique.client
|
|
336
|
-
documento = client.documents.find(documento_id)
|
|
337
|
-
|
|
338
|
-
if documento.signed?
|
|
339
|
-
update(status: :assinado)
|
|
340
|
-
elsif documento.rejected?
|
|
341
|
-
update(status: :rejeitado)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
documento
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Error Handling
|
|
91
|
+
## Error Handling
|
|
350
92
|
|
|
351
93
|
```ruby
|
|
352
94
|
begin
|
|
353
|
-
document = client.documents.create(
|
|
354
|
-
file: 'contract.pdf',
|
|
355
|
-
document: { name: 'Contract' },
|
|
356
|
-
signers: [{ email: 'invalid@email', action: 'SIGN' }]
|
|
357
|
-
)
|
|
95
|
+
document = client.documents.create(...)
|
|
358
96
|
rescue Autentique::AuthenticationError => e
|
|
359
|
-
|
|
97
|
+
# Invalid or missing API key
|
|
360
98
|
rescue Autentique::RateLimitError => e
|
|
361
|
-
|
|
99
|
+
# 60 requests/minute limit exceeded
|
|
362
100
|
rescue Autentique::ValidationError => e
|
|
363
|
-
|
|
101
|
+
# Invalid input
|
|
364
102
|
rescue Autentique::QueryError => e
|
|
365
|
-
|
|
366
|
-
puts "Errors: #{e.errors.inspect}"
|
|
103
|
+
# GraphQL query failed — e.errors contains details
|
|
367
104
|
rescue Autentique::UploadError => e
|
|
368
|
-
|
|
105
|
+
# File upload failed
|
|
106
|
+
rescue Autentique::InvalidSignatureError => e
|
|
107
|
+
# Webhook signature verification failed
|
|
108
|
+
rescue Autentique::WebhookError => e
|
|
109
|
+
# Unknown webhook event type or processing error
|
|
369
110
|
rescue Autentique::Error => e
|
|
370
|
-
|
|
111
|
+
# Base class for all gem errors
|
|
371
112
|
end
|
|
372
113
|
```
|
|
373
114
|
|
|
374
|
-
##
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
- ✅ List pending documents
|
|
381
|
-
- ✅ List all documents with filters
|
|
382
|
-
- ✅ Delete documents
|
|
383
|
-
- ✅ List folders
|
|
384
|
-
- ✅ Create folders
|
|
385
|
-
- ✅ Delete folders
|
|
386
|
-
- ✅ Sandbox mode support
|
|
387
|
-
- ✅ All signer options
|
|
388
|
-
- ✅ Security verifications
|
|
389
|
-
- ✅ Signature positioning
|
|
390
|
-
- ✅ Document configurations
|
|
391
|
-
|
|
392
|
-
### Roadmap
|
|
393
|
-
|
|
394
|
-
- ⏳ Webhooks support
|
|
395
|
-
- ⏳ Document templates
|
|
396
|
-
- ⏳ Bulk operations
|
|
397
|
-
- ⏳ Organization management
|
|
398
|
-
- ⏳ User management
|
|
399
|
-
|
|
400
|
-
## Configuration Options
|
|
401
|
-
|
|
402
|
-
### Document Options
|
|
403
|
-
|
|
404
|
-
| Option | Type | Description |
|
|
405
|
-
|--------|------|-------------|
|
|
406
|
-
| `name` | String | Document name (required) |
|
|
407
|
-
| `message` | String | Custom message for signers |
|
|
408
|
-
| `reminder` | String | Reminder frequency (`WEEKLY`, `DAILY`) |
|
|
409
|
-
| `sortable` | Boolean | Signers must sign in order |
|
|
410
|
-
| `footer` | String | Footer position (`BOTTOM`, `LEFT`, `RIGHT`) |
|
|
411
|
-
| `refusable` | Boolean | Allow document rejection |
|
|
412
|
-
| `qualified` | Boolean | Enable qualified signatures |
|
|
413
|
-
| `scrolling_required` | Boolean | Require full scroll before signing |
|
|
414
|
-
| `stop_on_rejected` | Boolean | Stop when document is rejected |
|
|
415
|
-
| `new_signature_style` | Boolean | Use new signature fields |
|
|
416
|
-
| `show_audit_page` | Boolean | Show audit page |
|
|
417
|
-
| `ignore_cpf` | Boolean | Don't require CPF |
|
|
418
|
-
| `ignore_birthdate` | Boolean | Don't require birthdate |
|
|
419
|
-
| `deadline_at` | DateTime | Signing deadline |
|
|
420
|
-
|
|
421
|
-
### Signer Options
|
|
422
|
-
|
|
423
|
-
| Option | Type | Description |
|
|
424
|
-
|--------|------|-------------|
|
|
425
|
-
| `email` | String | Signer's email |
|
|
426
|
-
| `phone` | String | Signer's phone (for SMS/WhatsApp) |
|
|
427
|
-
| `name` | String | Signer's name (for link-based signing) |
|
|
428
|
-
| `action` | String | Action type (required) |
|
|
429
|
-
| `delivery_method` | String | Delivery method for phone signers |
|
|
430
|
-
| `configs` | Hash | Additional configs (e.g., CPF) |
|
|
431
|
-
| `security_verifications` | Array | Security checks |
|
|
432
|
-
| `positions` | Array | Signature field positions |
|
|
433
|
-
|
|
434
|
-
## Rate Limiting
|
|
435
|
-
|
|
436
|
-
The Autentique API has a rate limit of **60 requests per minute**. The gem automatically handles rate limit errors.
|
|
115
|
+
## Documentation
|
|
116
|
+
|
|
117
|
+
- [Documents](docs/documents.md) — creating, retrieving, listing, deleting, and rejecting documents
|
|
118
|
+
- [Folders](docs/folders.md) — folder management
|
|
119
|
+
- [Webhooks](docs/webhooks.md) — processing incoming webhook events
|
|
120
|
+
- [Configuration Reference](docs/configuration.md) — all document and signer options
|
|
437
121
|
|
|
438
122
|
## Testing
|
|
439
123
|
|
|
440
124
|
```bash
|
|
441
|
-
# Install dependencies
|
|
442
|
-
bundle install
|
|
443
|
-
|
|
444
|
-
# Run tests
|
|
445
125
|
bundle exec rspec
|
|
446
|
-
|
|
447
|
-
# Run with coverage
|
|
448
126
|
COVERAGE=true bundle exec rspec
|
|
449
127
|
```
|
|
450
128
|
|
|
451
129
|
## Development
|
|
452
130
|
|
|
453
131
|
```bash
|
|
454
|
-
|
|
455
|
-
git clone https://github.com/yourusername/autentique-ruby.git
|
|
132
|
+
git clone https://github.com/keithyoder/autentique-ruby.git
|
|
456
133
|
cd autentique-ruby
|
|
457
|
-
|
|
458
|
-
# Install dependencies
|
|
459
134
|
bundle install
|
|
460
|
-
|
|
461
|
-
# Run tests
|
|
462
135
|
bundle exec rspec
|
|
463
|
-
|
|
464
|
-
# Run console
|
|
465
|
-
bin/console
|
|
466
|
-
|
|
467
|
-
# Build gem
|
|
468
136
|
gem build autentique.gemspec
|
|
469
|
-
|
|
470
|
-
# Install locally
|
|
471
|
-
gem install autentique-0.1.0.gem
|
|
472
137
|
```
|
|
473
138
|
|
|
474
139
|
## Contributing
|
|
475
140
|
|
|
476
141
|
1. Fork the repository
|
|
477
|
-
2. Create your feature branch (`git checkout -b feature/
|
|
478
|
-
3. Commit your changes (`git commit -am 'Add
|
|
479
|
-
4. Push to the branch (`git push origin feature/
|
|
142
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
143
|
+
3. Commit your changes (`git commit -am 'Add my feature'`)
|
|
144
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
480
145
|
5. Open a Pull Request
|
|
481
146
|
|
|
482
147
|
## Resources
|
|
483
148
|
|
|
484
|
-
- [Autentique
|
|
149
|
+
- [Autentique API Documentation](https://docs.autentique.com.br/api)
|
|
485
150
|
- [Autentique Dashboard](https://painel.autentique.com.br)
|
|
486
|
-
- [GraphQL
|
|
151
|
+
- [GraphQL Explorer](https://altair.autentique.com.br)
|
|
487
152
|
- [API Keys](https://painel.autentique.com.br/perfil/api)
|
|
488
153
|
|
|
489
154
|
## License
|
|
490
155
|
|
|
491
|
-
|
|
156
|
+
Available as open source under the [MIT License](LICENSE).
|
|
492
157
|
|
|
493
158
|
## Acknowledgments
|
|
494
159
|
|
|
495
|
-
This gem is not officially maintained by Autentique. It
|
|
160
|
+
This gem is not officially maintained by Autentique. It is a community-driven project to simplify Ruby integration with the Autentique API.
|
|
496
161
|
|
|
497
162
|
## Support
|
|
498
163
|
|
|
499
|
-
- 🐛 Report bugs
|
|
500
|
-
- 💬
|
|
164
|
+
- 🐛 [Report bugs](https://github.com/keithyoder/autentique-ruby/issues)
|
|
165
|
+
- 💬 [Ask questions](https://github.com/keithyoder/autentique-ruby/discussions)
|
|
501
166
|
|
|
502
167
|
## Changelog
|
|
503
168
|
|
|
504
|
-
See [CHANGELOG.md](CHANGELOG.md)
|
|
169
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
data/lib/autentique/errors.rb
CHANGED
|
@@ -32,7 +32,9 @@ module Autentique
|
|
|
32
32
|
result = client.query(query, variables: { limit: limit, page: page })
|
|
33
33
|
raise QueryError.new('Query failed', result.errors.messages) if result.errors.any?
|
|
34
34
|
|
|
35
|
-
result.data.documents&.data&.map { |doc| Models::Document.new(doc.to_h) } || []
|
|
35
|
+
docs = result.data.documents&.data&.map { |doc| Models::Document.new(doc.to_h) } || []
|
|
36
|
+
total = result.data.documents&.total || 0
|
|
37
|
+
{ documents: docs, total: total }
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Autentique
|
|
4
|
+
module Resources
|
|
5
|
+
class Documents
|
|
6
|
+
module Reject
|
|
7
|
+
def reject(id, reason: nil)
|
|
8
|
+
query = client.graphql_client.parse <<~GRAPHQL
|
|
9
|
+
mutation($id: UUID!, $reason: String) {
|
|
10
|
+
rejectDocument(id: $id, reason: $reason)
|
|
11
|
+
}
|
|
12
|
+
GRAPHQL
|
|
13
|
+
|
|
14
|
+
variables = { id: id }
|
|
15
|
+
variables[:reason] = reason if reason
|
|
16
|
+
|
|
17
|
+
result = client.query(query, variables: variables)
|
|
18
|
+
raise QueryError.new('Query failed', result.errors.messages) if result.errors.any?
|
|
19
|
+
|
|
20
|
+
result.data.reject_document
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -9,6 +9,7 @@ require_relative 'documents/find'
|
|
|
9
9
|
require_relative 'documents/pending'
|
|
10
10
|
require_relative 'documents/list'
|
|
11
11
|
require_relative 'documents/delete'
|
|
12
|
+
require_relative 'documents/reject'
|
|
12
13
|
|
|
13
14
|
module Autentique
|
|
14
15
|
module Resources
|
|
@@ -18,6 +19,7 @@ module Autentique
|
|
|
18
19
|
include Documents::Pending
|
|
19
20
|
include Documents::List
|
|
20
21
|
include Documents::Delete
|
|
22
|
+
include Documents::Reject
|
|
21
23
|
|
|
22
24
|
attr_reader :client
|
|
23
25
|
|
data/lib/autentique/version.rb
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Autentique
|
|
7
|
+
class WebhookProcessor
|
|
8
|
+
SUPPORTED_EVENTS = %w[
|
|
9
|
+
document.created document.updated document.deleted document.finished
|
|
10
|
+
signature.created signature.updated signature.deleted signature.viewed
|
|
11
|
+
signature.accepted signature.rejected signature.biometric_approved
|
|
12
|
+
signature.biometric_unapproved signature.biometric_rejected signature.delivery_failed
|
|
13
|
+
member.created member.deleted
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :payload, :event_type
|
|
17
|
+
|
|
18
|
+
def initialize(body, secret: nil, signature: nil)
|
|
19
|
+
@secret = secret
|
|
20
|
+
@signature = signature
|
|
21
|
+
@payload = parse_payload(body)
|
|
22
|
+
@event_type = @payload['event']['type']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Verify HMAC-SHA256 signature from the X-Autentique-Signature header
|
|
26
|
+
def valid_signature?
|
|
27
|
+
return true if @secret.nil?
|
|
28
|
+
|
|
29
|
+
expected = OpenSSL::HMAC.hexdigest('SHA256', @secret, @payload.to_json)
|
|
30
|
+
if defined?(ActiveSupport::SecurityUtils)
|
|
31
|
+
ActiveSupport::SecurityUtils.secure_compare(expected, @signature.to_s)
|
|
32
|
+
else
|
|
33
|
+
expected == @signature.to_s
|
|
34
|
+
end
|
|
35
|
+
rescue StandardError
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Process the webhook, yielding event_type and event data to the block
|
|
40
|
+
def process
|
|
41
|
+
raise InvalidSignatureError, 'Webhook signature verification failed' unless valid_signature?
|
|
42
|
+
raise WebhookError, "Unknown event: #{event_type}" unless SUPPORTED_EVENTS.include?(event_type)
|
|
43
|
+
|
|
44
|
+
yield(event_type, event_data) if block_given?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The event object from the payload
|
|
48
|
+
def event
|
|
49
|
+
@payload['event']
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The data object from the event (the document, signature, or member)
|
|
53
|
+
def event_data
|
|
54
|
+
@payload['event']['data']
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The previous attributes for *.updated events
|
|
58
|
+
def previous_attributes
|
|
59
|
+
@payload['event']['previous_attributes']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Convenience predicates
|
|
63
|
+
def document_event?
|
|
64
|
+
event_type.to_s.start_with?('document.')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def signature_event?
|
|
68
|
+
event_type.to_s.start_with?('signature.')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def member_event?
|
|
72
|
+
event_type.to_s.start_with?('member.')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def parse_payload(body)
|
|
78
|
+
JSON.parse(body)
|
|
79
|
+
rescue JSON::ParserError
|
|
80
|
+
raise ArgumentError, 'Invalid webhook payload: could not parse as JSON'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/autentique.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative 'autentique/models/document'
|
|
|
7
7
|
require_relative 'autentique/resources'
|
|
8
8
|
require_relative 'autentique/resources/documents'
|
|
9
9
|
require_relative 'autentique/resources/folders'
|
|
10
|
+
require_relative 'autentique/webhook_processor'
|
|
10
11
|
|
|
11
12
|
# Main module for the Autentique gem
|
|
12
13
|
#
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: autentique
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Yoder
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: graphql-client
|
|
@@ -65,8 +65,10 @@ files:
|
|
|
65
65
|
- lib/autentique/resources/documents/find.rb
|
|
66
66
|
- lib/autentique/resources/documents/list.rb
|
|
67
67
|
- lib/autentique/resources/documents/pending.rb
|
|
68
|
+
- lib/autentique/resources/documents/reject.rb
|
|
68
69
|
- lib/autentique/resources/folders.rb
|
|
69
70
|
- lib/autentique/version.rb
|
|
71
|
+
- lib/autentique/webhook_processor.rb
|
|
70
72
|
homepage: https://github.com/keithyoder/autentique-ruby
|
|
71
73
|
licenses:
|
|
72
74
|
- MIT
|