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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21da834df25916c1cdc87f2818c3c91dded5682fd749b7e637ec9e3b76e434b7
4
- data.tar.gz: d6d71199c1fc0d27778d07888ecea49fbc16e33711741c9dfb29eb7496b3bf0e
3
+ metadata.gz: ddb99e141936ac88e36ddf58b662d42fea5bdf3c9efba7f49d278dc87052dab9
4
+ data.tar.gz: '0938f943bba5d36e839189709176567cf0e15dd17372c7952b176adc7990eb98'
5
5
  SHA512:
6
- metadata.gz: fe44cfff47087f92184b7b5b14b66f72416ef748e1b09ba5fe0badecf4654c8d4af083ee11393ba421e4fbda596a01378ed027b272ce71f3073e4f052019f2a5
7
- data.tar.gz: f17affdb8233040fe3c6eeab0895c44f6ffb85d9eb53353751738fbc0402e011249b96815a8bc62cca2a418ec234000677a6614fd038896781866aa68dfb297c
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
- ## [Unreleased]
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
- ### Added
13
- - Initial release of the Autentique Ruby gem
14
- - Client class for API communication
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
- - Project structure
58
- - Basic gem scaffolding
59
-
60
- [Unreleased]: https://github.com/yourusername/autentique-ruby/compare/v0.1.0...HEAD
61
- [0.1.0]: https://github.com/yourusername/autentique-ruby/releases/tag/v0.1.0
62
- [0.0.1]: https://github.com/yourusername/autentique-ruby/releases/tag/v0.0.1
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 Ruby interface to Autentique's GraphQL API for document signing and management.
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
  [![CI](https://github.com/keithyoder/autentique-ruby/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/keithyoder/autentique-ruby/actions/workflows/ci.yml)
6
6
  [![Security](https://github.com/keithyoder/autentique-ruby/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/keithyoder/autentique-ruby/actions/workflows/security.yml)
7
- [![Gem Version](https://badge.fury.io/rb/nfcom.svg)](https://badge.fury.io/rb/autentique)
7
+ [![Gem Version](https://badge.fury.io/rb/autentique.svg)](https://badge.fury.io/rb/autentique)
8
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.7.0-ruby.svg)](https://www.ruby-lang.org)
8
9
 
9
10
  ## Features
10
11
 
11
- - 🔐 **Simple Authentication** - Easy API key configuration
12
- - 📄 **Document Management** - Create, retrieve, list, and delete documents
13
- - ✍️ **Flexible Signing** - Support for multiple signers with various authentication methods
14
- - 🏖️ **Sandbox Mode** - Test without consuming document credits
15
- - 🔍 **Advanced Queries** - Full GraphQL query support
16
- - 📁 **Folder Management** - Organize documents in folders
17
- - 🛡️ **Type Safety** - Model classes for structured data
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 this line to your application's Gemfile:
22
+ Add to your Gemfile:
23
23
 
24
24
  ```ruby
25
25
  gem 'autentique'
26
26
  ```
27
27
 
28
- And then execute:
28
+ Then run `bundle install`, or install directly with `gem install autentique`.
29
29
 
30
- ```bash
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
- name: 'Employment Contract'
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 "Document created: #{document.id}"
96
- puts "Signature link: #{document.signatures.first.short_link}"
97
- ```
42
+ puts document.id
43
+ puts document.signatures.first.short_link
98
44
 
99
- #### Advanced Document Creation
45
+ # Retrieve a document
46
+ doc = client.documents.find('document-uuid')
47
+ puts doc.signed? ? 'Signed' : 'Pending'
100
48
 
101
- ```ruby
102
- document = client.documents.create(
103
- file: File.open('/path/to/contract.pdf'),
104
- document: {
105
- name: 'Marketing Contract',
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
- ```ruby
204
- # List pending documents
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
- ### Sandbox Mode
61
+ ## Configuration
239
62
 
240
- Test without consuming document credits:
63
+ ### Environment variable
241
64
 
242
65
  ```ruby
243
- # Enable sandbox globally
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
- ### Using Model Classes
259
-
260
- For better type safety and IDE support:
69
+ ### Global configuration (recommended for Rails)
261
70
 
262
71
  ```ruby
263
- # Use DocumentInput model
264
- doc_input = Autentique::Models::DocumentInput.new(
265
- name: 'Contract',
266
- reminder: 'WEEKLY',
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
- document = client.documents.create(
280
- file: 'contract.pdf',
281
- document: doc_input,
282
- signers: [signer]
283
- )
77
+ client = Autentique.client
284
78
  ```
285
79
 
286
- ### Rails Integration
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
- #### In Your Models
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
- puts "Authentication failed: #{e.message}"
97
+ # Invalid or missing API key
360
98
  rescue Autentique::RateLimitError => e
361
- puts "Rate limit exceeded: #{e.message}"
99
+ # 60 requests/minute limit exceeded
362
100
  rescue Autentique::ValidationError => e
363
- puts "Validation error: #{e.message}"
101
+ # Invalid input
364
102
  rescue Autentique::QueryError => e
365
- puts "Query failed: #{e.message}"
366
- puts "Errors: #{e.errors.inspect}"
103
+ # GraphQL query failed e.errors contains details
367
104
  rescue Autentique::UploadError => e
368
- puts "Upload failed: #{e.message}"
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
- puts "General error: #{e.message}"
111
+ # Base class for all gem errors
371
112
  end
372
113
  ```
373
114
 
374
- ## API Coverage
375
-
376
- ### Implemented
377
-
378
- - Create documents with file upload
379
- - Retrieve document by ID
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
- # Clone the repository
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/amazing-feature`)
478
- 3. Commit your changes (`git commit -am 'Add amazing feature'`)
479
- 4. Push to the branch (`git push origin feature/amazing-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 Official Documentation](https://docs.autentique.com.br/api)
149
+ - [Autentique API Documentation](https://docs.autentique.com.br/api)
485
150
  - [Autentique Dashboard](https://painel.autentique.com.br)
486
- - [GraphQL Altair Explorer](https://altair.autentique.com.br)
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
- This gem is available as open source under the terms of the [MIT License](LICENSE).
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's a community-driven project to make integration easier for Ruby developers.
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: [GitHub Issues](https://github.com/yourusername/autentique-ruby/issues)
500
- - 💬 Questions: [GitHub Discussions](https://github.com/yourusername/autentique-ruby/discussions)
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) for a list of changes.
169
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -28,4 +28,10 @@ module Autentique
28
28
  @errors = errors
29
29
  end
30
30
  end
31
+
32
+ # Raised when a webhook signature is invalid
33
+ class InvalidSignatureError < Error; end
34
+
35
+ # Raised for webhook processing errors
36
+ class WebhookError < Error; end
31
37
  end
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Autentique
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
@@ -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.0
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-09 00:00:00.000000000 Z
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