accessgrid 0.2.0 → 0.5.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: ab77e5bbf4a0a163fb5990dd7a0c6872c31bdbeb750bd7906b394d0d86e70d59
4
- data.tar.gz: 2e6dff141d2eb8bd4c605c5363b12d0494e587cd4bcd1dc7e8a856d745dfa4ef
3
+ metadata.gz: 5370a4fedb6bf2bb2dc99ec4a8b28148f1caca417aca9f2401192c06a5fd49f9
4
+ data.tar.gz: fbe42537ec52fef71f1cd9806f2d91896c65e8e2ae4ad08aa8b9b864c52cf6aa
5
5
  SHA512:
6
- metadata.gz: 2516e380d23a8b0e0bc988c5fb47c322707c0f2b98e5d82f1df4cfb500353bb56b131347e4bb9f899aa853d93d3acd6512edabe70f8c06b35d7a893804b3b1cb
7
- data.tar.gz: 94cd560394836b1be4b73541557715867dc3f92979563778448c93e26994a3ea93d2681a6704d01f1c585b7d5ffa8b8380a00efb2a25913cdb7a42da90ed5415
6
+ metadata.gz: c98e6d776bd2eede74712e6a6a08d0b87c67cc2161dc18748d84db28f34273b69aa6f8f2ce4d6bdb0a5f02c6a68a60cf1b043bed9a785402a373a3296e72521e
7
+ data.tar.gz: 6d8c089a3071b4064a5902823cfd8f88393fd0d60d80215a72860dd6e139d1a790b4a8a5b410a3311d54915b6a6c1fe94c7c3cc023d33c0a74691e2c886c1a1d
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # AccessGrid SDK
1
+ # ![AccessGrid Logo](accessgrid.png)
2
2
 
3
- A Ruby SDK for interacting with the [AccessGrid.com](https://www.accessgrid.com) API. This SDK provides a simple interface for managing NFC key cards and enterprise templates. Full docs at https://www.accessgrid.com/docs
3
+ AccessGrid is a Ruby SDK for interacting with the [AccessGrid.com](https://www.accessgrid.com) API. This SDK provides a simple interface for managing NFC key cards and enterprise templates. Full docs at https://www.accessgrid.com/docs
4
4
 
5
5
  ## Installation
6
6
 
@@ -43,15 +43,25 @@ client = AccessGrid.new(account_id, secret_key)
43
43
  card = client.access_cards.issue(
44
44
  card_template_id: card_template_id,
45
45
  employee_id: "123456789",
46
- card_number: "16187",
47
46
  tag_id: "DDEADB33FB00B5",
48
47
  full_name: "Employee name",
49
48
  email: "employee@yourwebsite.com",
50
49
  phone_number: "+19547212241",
51
50
  classification: "full_time",
52
- start_date: "2025-01-31T22:46:25.601Z",
53
- expiration_date: "2025-04-30T22:46:25.601Z",
54
- employee_photo: "[image_in_base64_encoded_format]"
51
+ department: "Engineering",
52
+ location: "San Francisco",
53
+ site_name: "HQ Building A",
54
+ workstation: "4F-207",
55
+ mail_stop: "MS-401",
56
+ company_address: "123 Main St, San Francisco, CA 94105",
57
+ start_date: Time.now.utc.iso8601(3),
58
+ expiration_date: 3.months.from_now.utc.iso8601(3),
59
+ employee_photo: "[image_in_base64_encoded_format]",
60
+ title: "Engineering Manager",
61
+ metadata: {
62
+ "department": "engineering",
63
+ "badge_type": "contractor"
64
+ }
55
65
  )
56
66
 
57
67
  # Provision is an alias for issue (for backwards compatibility)
@@ -127,27 +137,24 @@ client.access_cards.delete("0xc4rd1d")
127
137
 
128
138
  ```ruby
129
139
  template = client.console.create_template(
130
- name: "Employee NFC key",
140
+ name: "Employee Access Pass",
131
141
  platform: "apple",
132
- use_case: "employee_badge",
142
+ use_case: "corporate_id",
133
143
  protocol: "desfire",
134
144
  allow_on_multiple_devices: true,
135
145
  watch_count: 2,
136
146
  iphone_count: 3,
137
- design: {
138
- background_color: "#FFFFFF",
139
- label_color: "#000000",
140
- label_secondary_color: "#333333",
141
- background_image: "[image_in_base64_encoded_format]",
142
- logo_image: "[image_in_base64_encoded_format]",
143
- icon_image: "[image_in_base64_encoded_format]"
144
- },
145
- support_info: {
146
- support_url: "https://help.yourcompany.com",
147
- support_phone_number: "+1-555-123-4567",
148
- support_email: "support@yourcompany.com",
149
- privacy_policy_url: "https://yourcompany.com/privacy",
150
- terms_and_conditions_url: "https://yourcompany.com/terms"
147
+ background_color: "#FFFFFF",
148
+ label_color: "#000000",
149
+ label_secondary_color: "#333333",
150
+ support_url: "https://help.yourcompany.com",
151
+ support_phone_number: "+1-555-123-4567",
152
+ support_email: "support@yourcompany.com",
153
+ privacy_policy_url: "https://yourcompany.com/privacy",
154
+ terms_and_conditions_url: "https://yourcompany.com/terms",
155
+ metadata: {
156
+ version: "2.1",
157
+ approval_status: "approved"
151
158
  }
152
159
  )
153
160
  ```
@@ -159,7 +166,6 @@ template = client.console.update_template(
159
166
  "0xd3adb00b5",
160
167
  {
161
168
  name: "Updated Employee NFC key",
162
- allow_on_multiple_devices: true,
163
169
  watch_count: 2,
164
170
  iphone_count: 3,
165
171
  support_info: {
@@ -179,6 +185,30 @@ template = client.console.update_template(
179
185
  template = client.console.read_template("0xd3adb00b5")
180
186
  ```
181
187
 
188
+ #### Publish a template
189
+
190
+ ```ruby
191
+ result = client.console.publish_template("0xd3adb00b5")
192
+
193
+ puts result.id # "0xd3adb00b5"
194
+ puts result.status # "in-review" (Apple), "ready" (Android), or "publishing" (already in flight)
195
+ ```
196
+
197
+ #### Reveal a SmartTap private key
198
+
199
+ Fetches the template's SmartTap private key, decrypted client-side. The SDK generates a fresh ephemeral P-256 keypair per call, submits the public half, and decrypts the server's response — you get the plaintext PEM back without touching any crypto.
200
+
201
+ ```ruby
202
+ reveal = client.console.reveal_smart_tap("0xd3adb00b5")
203
+
204
+ puts "Key version: #{reveal.key_version}"
205
+ puts "Collector ID: #{reveal.collector_id}"
206
+ puts "Fingerprint: #{reveal.fingerprint}"
207
+ puts reveal.private_key # PEM — store in your reader/collector key vault
208
+ ```
209
+
210
+ The server enforces single-use on pubkey fingerprint and rate-limits to 1 per minute per account. The SDK uses a fresh keypair every call, so single-use is satisfied automatically. Errors raised by the crypto path (`AccessGrid::DecryptError`, `AccessGrid::InvalidEnvelopeError`) and the HTTP path (`AccessGrid::AuthenticationError`, `AccessGrid::ResourceNotFoundError`, etc.) all descend from `AccessGrid::Error`.
211
+
182
212
  #### Get event logs
183
213
 
184
214
  ```ruby
@@ -205,6 +235,199 @@ events = client.console.event_log(
205
235
  )
206
236
  ```
207
237
 
238
+ #### List pass template pairs
239
+
240
+ ```ruby
241
+ response = client.console.list_pass_template_pairs(page: 1, per_page: 20)
242
+
243
+ response['card_template_pairs'].each do |pair|
244
+ puts "#{pair.name} (#{pair.id})"
245
+ puts " iOS: #{pair.ios_template&.name}"
246
+ puts " Android: #{pair.android_template&.name}"
247
+ end
248
+
249
+ puts response['pagination'] # { "current_page" => 1, "total_pages" => 5, ... }
250
+ ```
251
+
252
+ #### List ledger items
253
+
254
+ ```ruby
255
+ response = client.console.list_ledger_items(
256
+ page: 1,
257
+ per_page: 50,
258
+ start_date: '2025-01-01T00:00:00Z',
259
+ end_date: '2025-06-30T23:59:59Z'
260
+ )
261
+
262
+ response['ledger_items'].each do |item|
263
+ puts "#{item.kind}: #{item.amount} (#{item.created_at})"
264
+
265
+ if item.access_pass
266
+ puts " Pass: #{item.access_pass.full_name} (#{item.access_pass.state})"
267
+ if item.access_pass.pass_template
268
+ puts " Template: #{item.access_pass.pass_template.name}"
269
+ end
270
+ end
271
+ end
272
+
273
+ puts response['pagination'] # { "current_page" => 1, "total_pages" => 3, ... }
274
+ ```
275
+
276
+ #### iOS In-App Provisioning Preflight
277
+
278
+ ```ruby
279
+ response = client.console.ios_preflight(
280
+ card_template_id: "0xt3mp14t3-3x1d",
281
+ access_pass_ex_id: "0xp455-3x1d"
282
+ )
283
+
284
+ puts "Provisioning Credential ID: #{response.provisioning_credential_identifier}"
285
+ puts "Sharing Instance ID: #{response.sharing_instance_identifier}"
286
+ puts "Card Template ID: #{response.card_template_identifier}"
287
+ puts "Environment ID: #{response.environment_identifier}"
288
+ ```
289
+
290
+ ### Webhooks
291
+
292
+ #### Create a webhook
293
+
294
+ ```ruby
295
+ webhook = client.console.webhooks.create(
296
+ name: 'Production',
297
+ url: 'https://example.com/webhooks',
298
+ subscribed_events: ['ag.access_pass.issued']
299
+ )
300
+
301
+ puts "Webhook created: #{webhook.id}"
302
+ puts "Private key: #{webhook.private_key}"
303
+ ```
304
+
305
+ #### List webhooks
306
+
307
+ ```ruby
308
+ webhooks = client.console.webhooks.list
309
+
310
+ webhooks.each do |webhook|
311
+ puts "ID: #{webhook.id}, Name: #{webhook.name}"
312
+ end
313
+ ```
314
+
315
+ #### Delete a webhook
316
+
317
+ ```ruby
318
+ client.console.webhooks.delete('abc123')
319
+ ```
320
+
321
+ ### HID Organizations
322
+
323
+ #### Create an HID org
324
+
325
+ ```ruby
326
+ org = client.console.hid.orgs.create(
327
+ name: 'My Org',
328
+ full_address: '1 Main St, NY NY',
329
+ phone: '+1-555-0000',
330
+ first_name: 'Ada',
331
+ last_name: 'Lovelace'
332
+ )
333
+
334
+ puts "Created org: #{org.name} (ID: #{org.id})"
335
+ puts "Slug: #{org.slug}"
336
+ ```
337
+
338
+ #### List HID orgs
339
+
340
+ ```ruby
341
+ orgs = client.console.hid.orgs.list
342
+
343
+ orgs.each do |org|
344
+ puts "Org ID: #{org.id}, Name: #{org.name}, Slug: #{org.slug}"
345
+ end
346
+ ```
347
+
348
+ #### Activate an HID org
349
+
350
+ ```ruby
351
+ result = client.console.hid.orgs.activate(
352
+ email: 'admin@example.com',
353
+ password: 'hid-password-123'
354
+ )
355
+
356
+ puts "Completed registration for org: #{result.name}"
357
+ puts "Status: #{result.status}"
358
+ ```
359
+
360
+ ### Landing Pages
361
+
362
+ #### List landing pages
363
+
364
+ ```ruby
365
+ landing_pages = client.console.list_landing_pages
366
+
367
+ landing_pages.each do |page|
368
+ puts "ID: #{page.id}, Name: #{page.name}, Kind: #{page.kind}"
369
+ puts " Password Protected: #{page.password_protected}"
370
+ puts " Logo URL: #{page.logo_url}" if page.logo_url
371
+ end
372
+ ```
373
+
374
+ #### Create a landing page
375
+
376
+ ```ruby
377
+ landing_page = client.console.create_landing_page(
378
+ name: "Miami Office Access Pass",
379
+ kind: "universal",
380
+ additional_text: "Welcome to the Miami Office",
381
+ bg_color: "#f1f5f9",
382
+ allow_immediate_download: true
383
+ )
384
+
385
+ puts "Landing page created: #{landing_page.id}"
386
+ puts "Name: #{landing_page.name}, Kind: #{landing_page.kind}"
387
+ ```
388
+
389
+ #### Update a landing page
390
+
391
+ ```ruby
392
+ landing_page = client.console.update_landing_page(
393
+ landing_page_id: "0xlandingpage1d",
394
+ name: "Updated Miami Office Access Pass",
395
+ additional_text: "Welcome! Tap below to get your access pass.",
396
+ bg_color: "#e2e8f0"
397
+ )
398
+
399
+ puts "Landing page updated: #{landing_page.id}"
400
+ puts "Name: #{landing_page.name}"
401
+ ```
402
+
403
+ ### Credential Profiles
404
+
405
+ #### List credential profiles
406
+
407
+ ```ruby
408
+ profiles = client.console.credential_profiles.list
409
+
410
+ profiles.each do |profile|
411
+ puts "ID: #{profile.id}, Name: #{profile.name}, AID: #{profile.aid}"
412
+ end
413
+ ```
414
+
415
+ #### Create a credential profile
416
+
417
+ ```ruby
418
+ profile = client.console.credential_profiles.create(
419
+ name: 'Main Office Profile',
420
+ app_name: 'KEY-ID-main',
421
+ keys: [
422
+ { value: 'your_32_char_hex_master_key_here' },
423
+ { value: 'your_32_char_hex__read_key__here' }
424
+ ]
425
+ )
426
+
427
+ puts "Profile created: #{profile.id}"
428
+ puts "AID: #{profile.aid}"
429
+ ```
430
+
208
431
  ## Configuration
209
432
 
210
433
  The SDK can be configured with a custom API endpoint:
@@ -242,7 +465,7 @@ end
242
465
 
243
466
  ## Requirements
244
467
 
245
- - Ruby 2.6 or higher
468
+ - Ruby 2.19 or higher
246
469
 
247
470
  ## Security
248
471
 
@@ -262,6 +485,40 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
262
485
 
263
486
  Bug reports and pull requests are welcome on GitHub at https://github.com/access-grid/accessgrid-rb.
264
487
 
488
+ ## Feature Matrix
489
+
490
+ | Endpoint | Method | Supported |
491
+ |---|---|:---:|
492
+ | POST /v1/key-cards | `access_cards.issue()` | Y |
493
+ | GET /v1/key-cards/{id} | `access_cards.get()` | Y |
494
+ | PATCH /v1/key-cards/{id} | `access_cards.update()` | Y |
495
+ | GET /v1/key-cards | `access_cards.list()` | Y |
496
+ | POST /v1/key-cards/{id}/suspend | `access_cards.suspend()` | Y |
497
+ | POST /v1/key-cards/{id}/resume | `access_cards.resume()` | Y |
498
+ | POST /v1/key-cards/{id}/unlink | `access_cards.unlink()` | Y |
499
+ | POST /v1/key-cards/{id}/delete | `access_cards.delete()` | Y |
500
+ | POST /v1/console/card-templates | `console.create_template()` | Y |
501
+ | PUT /v1/console/card-templates/{id} | `console.update_template()` | Y |
502
+ | GET /v1/console/card-templates/{id} | `console.read_template()` | Y |
503
+ | GET /v1/console/card-templates/{id}/logs | `console.get_logs()` / `console.event_log()` | Y |
504
+ | GET /v1/console/card-template-pairs | `console.list_pass_template_pairs()` | Y |
505
+ | POST /v1/console/card-template-pairs | `console.create_pass_template_pair()` | Y |
506
+ | POST /v1/console/card-templates/{id}/ios_preflight | `console.ios_preflight()` | Y |
507
+ | POST /v1/console/card-templates/{id}/publish | `console.publish_template()` | Y |
508
+ | POST /v1/console/card-templates/{id}/smart-tap/reveal | `console.reveal_smart_tap()` | Y |
509
+ | GET /v1/console/ledger-items | `console.list_ledger_items()` / `console.ledger_items()` | Y |
510
+ | GET /v1/console/webhooks | `console.webhooks.list()` | Y |
511
+ | POST /v1/console/webhooks | `console.webhooks.create()` | Y |
512
+ | DELETE /v1/console/webhooks/{id} | `console.webhooks.delete()` | Y |
513
+ | GET /v1/console/landing-pages | `console.list_landing_pages()` | Y |
514
+ | POST /v1/console/landing-pages | `console.create_landing_page()` | Y |
515
+ | PUT /v1/console/landing-pages/{id} | `console.update_landing_page()` | Y |
516
+ | GET /v1/console/credential-profiles | `console.credential_profiles.list()` | Y |
517
+ | POST /v1/console/credential-profiles | `console.credential_profiles.create()` | Y |
518
+ | POST /v1/console/hid/orgs | `console.hid.orgs.create()` | Y |
519
+ | POST /v1/console/hid/orgs/activate | `console.hid.orgs.activate()` | Y |
520
+ | GET /v1/console/hid/orgs | `console.hid.orgs.list()` | Y |
521
+
265
522
  ## License
266
523
 
267
- The gem is available as open source under the terms of the MIT License.
524
+ The gem is available as open source under the terms of the MIT License.
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/accessgrid/access_cards.rb
2
4
  module AccessGrid
5
+ # Manages NFC key card lifecycle operations.
3
6
  class AccessCards
4
7
  def initialize(client)
5
8
  @client = client
@@ -9,7 +12,7 @@ module AccessGrid
9
12
  response = @client.make_request(:post, '/v1/key-cards', params)
10
13
  Card.new(response)
11
14
  end
12
-
15
+
13
16
  # Alias provision to issue for backward compatibility
14
17
  alias provision issue
15
18
 
@@ -22,24 +25,15 @@ module AccessGrid
22
25
  response = @client.make_request(:patch, "/v1/key-cards/#{card_id}", params)
23
26
  Card.new(response)
24
27
  end
25
-
28
+
26
29
  def list(template_id, state = nil)
27
30
  params = { template_id: template_id }
28
31
  params[:state] = state if state
29
-
32
+
30
33
  response = @client.make_request(:get, '/v1/key-cards', nil, params)
31
34
  response.fetch('keys', []).map { |item| Card.new(item) }
32
35
  end
33
36
 
34
- private def manage_state(card_id, action)
35
- response = @client.make_request(
36
- :post,
37
- "/v1/key-cards/#{card_id}/#{action}",
38
- {}
39
- )
40
- Card.new(response)
41
- end
42
-
43
37
  def suspend(card_id)
44
38
  manage_state(card_id, 'suspend')
45
39
  end
@@ -51,23 +45,36 @@ module AccessGrid
51
45
  def unlink(card_id)
52
46
  manage_state(card_id, 'unlink')
53
47
  end
54
-
48
+
55
49
  def delete(card_id)
56
50
  manage_state(card_id, 'delete')
57
51
  end
52
+
53
+ private
54
+
55
+ def manage_state(card_id, action)
56
+ response = @client.make_request(
57
+ :post,
58
+ "/v1/key-cards/#{card_id}/#{action}"
59
+ )
60
+ Card.new(response)
61
+ end
58
62
  end
59
63
 
64
+ # Represents an NFC key card with its attributes and state.
60
65
  class Card
61
66
  attr_reader :id, :state, :url, :install_url, :details, :full_name,
62
67
  :expiration_date, :card_template_id, :card_number, :site_code,
63
- :file_data, :direct_install_url, :devices, :metadata
68
+ :file_data, :direct_install_url, :devices, :metadata, :temporary,
69
+ :employee_id, :organization_name, :created_at
64
70
 
65
71
  def initialize(data)
66
72
  data ||= {}
73
+ install_url = data.fetch('install_url', nil)
67
74
  @id = data.fetch('id', nil)
68
75
  @state = data.fetch('state', nil)
69
- @url = data.fetch('install_url', nil)
70
- @install_url = data.fetch('install_url', nil)
76
+ @url = install_url
77
+ @install_url = install_url
71
78
  @details = data.fetch('details', nil)
72
79
  @full_name = data.fetch('full_name', nil)
73
80
  @expiration_date = data.fetch('expiration_date', nil)
@@ -78,6 +85,10 @@ module AccessGrid
78
85
  @direct_install_url = data.fetch('direct_install_url', nil)
79
86
  @devices = data.fetch('devices', [])
80
87
  @metadata = data.fetch('metadata', {})
88
+ @temporary = data.fetch('temporary', nil)
89
+ @employee_id = data.fetch('employee_id', nil)
90
+ @organization_name = data.fetch('organization_name', nil)
91
+ @created_at = data.fetch('created_at', nil)
81
92
  end
82
93
 
83
94
  def to_s
@@ -86,4 +97,4 @@ module AccessGrid
86
97
 
87
98
  alias inspect to_s
88
99
  end
89
- end
100
+ end