api_keys 0.1.0 → 0.2.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/CHANGELOG.md +8 -0
- data/README.md +69 -6
- data/app/controllers/api_keys/application_controller.rb +26 -32
- data/app/controllers/api_keys/keys_controller.rb +7 -7
- data/app/views/api_keys/keys/_form.html.erb +2 -2
- data/lib/api_keys/configuration.rb +7 -0
- data/lib/api_keys/version.rb +1 -1
- data/lib/api_keys.rb +43 -18
- data/lib/generators/api_keys/install_generator.rb +13 -2
- data/lib/generators/api_keys/templates/initializer.rb +25 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa734bb277bbb577d7e2ad46f6adc22c735c96d958ae85551385d61175153572
|
4
|
+
data.tar.gz: 8628007d90fc67798f388c2c8079ba1c98386ab43f764ac1c2948d141eaa5b85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7ffa6b96cc5e9bd5c02eacae8a2b542422c1b5182d0b5886ffa53b9a44860e4c7ad543af8d6c6459498c885942a37138c1f6a1384d94bd7f710cd32b8c5c9de
|
7
|
+
data.tar.gz: 46ab93c5d3df04e62dcd1c130f5c4cc7867626ede3beae5545d409ab57252c5801064f335aa429744c01671e43d8ee86a9ecef97d26b20ede49812e5ac7f3204
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## [0.2.0] - 2025-06-03
|
2
|
+
|
3
|
+
- Make gem owner-agnostic: API keys can now belong to any model (User, Organization, Team, etc.)
|
4
|
+
- Add flexible dashboard configuration for custom owner models
|
5
|
+
- Add support for multi-tenant and team-based API key ownership
|
6
|
+
- Improve documentation with common ownership scenarios
|
7
|
+
- Add configuration options for current_owner_method and authenticate_owner_method
|
8
|
+
|
1
9
|
## [0.1.0] - 2025-04-30
|
2
10
|
|
3
11
|
- Initial release
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# 🔑 `api_keys` –
|
1
|
+
# 🔑 `api_keys` – Secure API keys for your Rails app
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/api_keys)
|
4
4
|
|
5
|
-
`api_keys` makes it
|
5
|
+
`api_keys` makes it simple to add secure, production-ready API key authentication to any Rails app. Generate keys, restrict scopes, auto-expire tokens, revoke tokens, gate endpoints. It also provides a self-serve dashboard for your users to self-issue and manage their API keys themselves. All tokens are hashed securely by default, and never stored in plaintext.
|
6
6
|
|
7
7
|
[ 🟢 [Live interactive demo website](https://apikeys.rameerez.com) ]
|
8
8
|
|
@@ -50,9 +50,9 @@ end
|
|
50
50
|
|
51
51
|
It'd work the same if you want your `Organization` or your `Project` records to have API keys.
|
52
52
|
|
53
|
-
### Mount the dashboard engine
|
53
|
+
### Mount the dashboard engine so your users can self-serve API keys
|
54
54
|
|
55
|
-
The goal of `api_keys` is to allow you to turn your Rails app into an API platform with secure key authentication in minutes, as in: drop in this gem and you're pretty much done.
|
55
|
+
The goal of `api_keys` is to allow you to turn your Rails app into an API platform with secure key authentication in minutes, as in: drop in this gem and you're pretty much done with API key management.
|
56
56
|
|
57
57
|
To achieve that, the gem provides a ready-to-go dashboard you can just mount in your `routes.rb` like this:
|
58
58
|
|
@@ -60,7 +60,70 @@ To achieve that, the gem provides a ready-to-go dashboard you can just mount in
|
|
60
60
|
mount ApiKeys::Engine => '/settings/api-keys'
|
61
61
|
```
|
62
62
|
|
63
|
-
|
63
|
+
#### Default behavior (User-owned keys)
|
64
|
+
|
65
|
+
By default, the dashboard expects:
|
66
|
+
- A `current_user` method that returns the currently logged-in user
|
67
|
+
- An `authenticate_user!` method that ensures a user is logged in
|
68
|
+
|
69
|
+
This works out-of-the-box with Devise and most authentication solutions where `User` owns the API keys.
|
70
|
+
|
71
|
+
#### Custom owner models (Organization, Team, etc.)
|
72
|
+
|
73
|
+
If your API keys belong to a different model (e.g., `Organization`), you **must** configure the dashboard in your initializer:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# config/initializers/api_keys.rb
|
77
|
+
ApiKeys.configure do |config|
|
78
|
+
# Tell the dashboard how to find the current API key owner
|
79
|
+
config.current_owner_method = :current_organization
|
80
|
+
|
81
|
+
# Tell the dashboard how to ensure the owner is authenticated
|
82
|
+
config.authenticate_owner_method = :authenticate_organization!
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
These methods must exist in your `ApplicationController` (or wherever the engine is mounted). For example:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
class ApplicationController < ActionController::Base
|
90
|
+
def current_organization
|
91
|
+
# Your logic to return the current organization
|
92
|
+
@current_organization ||= Organization.find(session[:organization_id])
|
93
|
+
end
|
94
|
+
|
95
|
+
def authenticate_organization!
|
96
|
+
redirect_to login_path unless current_organization
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
#### Common scenarios
|
102
|
+
|
103
|
+
**Organization with user membership:**
|
104
|
+
```ruby
|
105
|
+
# When organizations own keys but users manage them
|
106
|
+
config.current_owner_method = :current_organization
|
107
|
+
config.authenticate_owner_method = :require_organization_member!
|
108
|
+
```
|
109
|
+
|
110
|
+
**Multi-tenant applications:**
|
111
|
+
```ruby
|
112
|
+
# When each tenant/account owns keys
|
113
|
+
config.current_owner_method = :current_account
|
114
|
+
config.authenticate_owner_method = :authenticate_account!
|
115
|
+
```
|
116
|
+
|
117
|
+
**Team-based ownership:**
|
118
|
+
```ruby
|
119
|
+
# When teams own keys
|
120
|
+
config.current_owner_method = :current_team
|
121
|
+
config.authenticate_owner_method = :ensure_team_access!
|
122
|
+
```
|
123
|
+
|
124
|
+
#### What the dashboard provides
|
125
|
+
|
126
|
+
Once configured, your users can:
|
64
127
|
- self-issue new API keys
|
65
128
|
- set expiration dates
|
66
129
|
- attach scopes / permissions to individual keys
|
@@ -13,50 +13,44 @@ module ApiKeys
|
|
13
13
|
# Include the main controller concern which bundles authentication and tenant resolution
|
14
14
|
include ApiKeys::Controller
|
15
15
|
|
16
|
-
# Ensure
|
17
|
-
#
|
18
|
-
|
19
|
-
# You might need to adjust this based on the host app's authentication system.
|
20
|
-
before_action :authenticate_api_keys_user!
|
16
|
+
# Ensure the owner is authenticated for all actions within this engine
|
17
|
+
# This uses the configured authentication method (defaults to authenticate_user!)
|
18
|
+
before_action :authenticate_api_keys_owner!
|
21
19
|
|
22
20
|
private
|
23
21
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# Try
|
30
|
-
if
|
31
|
-
|
32
|
-
elsif defined?(
|
33
|
-
|
22
|
+
# Authenticates the owner accessing the engine.
|
23
|
+
# Uses the configured authentication method from ApiKeys.configuration
|
24
|
+
def authenticate_api_keys_owner!
|
25
|
+
auth_method = ApiKeys.configuration.authenticate_owner_method
|
26
|
+
|
27
|
+
# Try to call the configured authentication method
|
28
|
+
if auth_method && respond_to?(auth_method, true)
|
29
|
+
send(auth_method)
|
30
|
+
elsif auth_method && defined?(auth_method)
|
31
|
+
send(auth_method)
|
34
32
|
else
|
35
|
-
# Fallback
|
36
|
-
unless
|
37
|
-
|
38
|
-
# Choose the appropriate action based on expected host app behavior
|
39
|
-
redirect_to main_app.root_path, alert: "You need to sign in or sign up before continuing." rescue render plain: "Unauthorized", status: :unauthorized
|
33
|
+
# Fallback: check if owner is present
|
34
|
+
unless current_api_keys_owner
|
35
|
+
redirect_to main_app.root_path, alert: "You need to sign in before continuing." rescue render plain: "Unauthorized", status: :unauthorized
|
40
36
|
end
|
41
37
|
end
|
42
38
|
end
|
43
39
|
|
44
|
-
# Helper method to access the current
|
45
|
-
#
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
if
|
50
|
-
|
51
|
-
elsif defined?(current_user)
|
52
|
-
current_user
|
40
|
+
# Helper method to access the current owner from the host application.
|
41
|
+
# Uses the configured method name (defaults to :current_user)
|
42
|
+
def current_api_keys_owner
|
43
|
+
owner_method = ApiKeys.configuration.current_owner_method
|
44
|
+
|
45
|
+
if owner_method && respond_to?(owner_method, true)
|
46
|
+
send(owner_method)
|
53
47
|
else
|
54
|
-
nil # No
|
48
|
+
nil # No owner context found
|
55
49
|
end
|
56
50
|
end
|
57
51
|
|
58
|
-
# Expose
|
59
|
-
helper_method :
|
52
|
+
# Expose current_api_keys_owner as a helper method for views
|
53
|
+
helper_method :current_api_keys_owner
|
60
54
|
|
61
55
|
end
|
62
56
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ApiKeys
|
4
|
-
# Controller for managing API keys belonging to the current
|
4
|
+
# Controller for managing API keys belonging to the current owner.
|
5
5
|
class KeysController < ApplicationController
|
6
6
|
before_action :set_api_key, only: [:show, :edit, :update, :revoke]
|
7
7
|
|
8
8
|
# GET /keys
|
9
9
|
def index
|
10
10
|
# Fetch only active keys for the main list, maybe sorted by creation date
|
11
|
-
@api_keys =
|
11
|
+
@api_keys = current_api_keys_owner.api_keys.active.order(created_at: :desc)
|
12
12
|
# Optionally, fetch inactive ones for a separate section or filter
|
13
|
-
@inactive_api_keys =
|
13
|
+
@inactive_api_keys = current_api_keys_owner.api_keys.inactive.order(created_at: :desc)
|
14
14
|
end
|
15
15
|
|
16
16
|
# GET /keys/:id
|
@@ -30,7 +30,7 @@ module ApiKeys
|
|
30
30
|
|
31
31
|
# GET /keys/new
|
32
32
|
def new
|
33
|
-
@api_key =
|
33
|
+
@api_key = current_api_keys_owner.api_keys.build
|
34
34
|
end
|
35
35
|
|
36
36
|
# POST /keys
|
@@ -38,7 +38,7 @@ module ApiKeys
|
|
38
38
|
# Use the HasApiKeys helper method to create the key
|
39
39
|
begin
|
40
40
|
# create_api_key! now returns the ApiKey instance
|
41
|
-
@api_key =
|
41
|
+
@api_key = current_api_keys_owner.create_api_key!(
|
42
42
|
name: api_key_params[:name],
|
43
43
|
scopes: api_key_params[:scopes],
|
44
44
|
expires_at: parse_expiration(api_key_params[:expires_at_preset])
|
@@ -59,7 +59,7 @@ module ApiKeys
|
|
59
59
|
render :new, status: :unprocessable_entity
|
60
60
|
rescue => e # Catch other potential errors
|
61
61
|
flash.now[:alert] = "An unexpected error occurred: #{e.message}"
|
62
|
-
@api_key =
|
62
|
+
@api_key = current_api_keys_owner.api_keys.build(api_key_params) # Rebuild form
|
63
63
|
render :new, status: :unprocessable_entity
|
64
64
|
end
|
65
65
|
end
|
@@ -93,7 +93,7 @@ module ApiKeys
|
|
93
93
|
|
94
94
|
# Use callbacks to share common setup or constraints between actions.
|
95
95
|
def set_api_key
|
96
|
-
@api_key =
|
96
|
+
@api_key = current_api_keys_owner.api_keys.find(params[:id])
|
97
97
|
rescue ActiveRecord::RecordNotFound
|
98
98
|
redirect_to keys_path, alert: "API key not found."
|
99
99
|
end
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<%# containing an array of scope strings allowed for this context. %>
|
39
39
|
<%# This list might come from global configuration or owner-specific settings. %>
|
40
40
|
<%# Example: @available_scopes = ["read", "write", "admin"] %>
|
41
|
-
<% @available_scopes =
|
41
|
+
<% @available_scopes = current_api_keys_owner.class.api_keys_settings.dig(:default_scopes) %>
|
42
42
|
<% if defined?(@available_scopes) && @available_scopes.present? %>
|
43
43
|
<div>
|
44
44
|
<%= form.label :scopes, "Permissions" %>
|
@@ -70,7 +70,7 @@
|
|
70
70
|
<%# Define available scopes for the edit form context %>
|
71
71
|
<%# This assumes the available scopes are the same as the default ones. %>
|
72
72
|
<%# Consider passing @available_scopes from the controller if logic is more complex. %>
|
73
|
-
<% @available_scopes =
|
73
|
+
<% @available_scopes = current_api_keys_owner.class.api_keys_settings.dig(:default_scopes) %>
|
74
74
|
<% if defined?(@available_scopes) && @available_scopes.present? %>
|
75
75
|
<div>
|
76
76
|
<%= form.label :scopes, "Permissions / Scopes" %>
|
@@ -24,6 +24,9 @@ module ApiKeys
|
|
24
24
|
# Engine Configuration
|
25
25
|
attr_accessor :parent_controller
|
26
26
|
|
27
|
+
# Owner Context Configuration
|
28
|
+
attr_accessor :current_owner_method, :authenticate_owner_method
|
29
|
+
|
27
30
|
# Optional Behaviors
|
28
31
|
attr_accessor :default_max_keys_per_owner, :require_key_name
|
29
32
|
attr_accessor :expire_after, :default_scopes, :track_requests_count
|
@@ -84,6 +87,10 @@ module ApiKeys
|
|
84
87
|
# Engine Configuration
|
85
88
|
@parent_controller = '::ApplicationController'
|
86
89
|
|
90
|
+
# Owner Context Configuration
|
91
|
+
@current_owner_method = :current_user # Default to current_user for backward compatibility
|
92
|
+
@authenticate_owner_method = :authenticate_user! # Default to authenticate_user! for Devise compatibility
|
93
|
+
|
87
94
|
# Optional Behaviors
|
88
95
|
@default_max_keys_per_owner = nil # No global key limit per owner
|
89
96
|
@require_key_name = false # Don't require names for keys globally
|
data/lib/api_keys/version.rb
CHANGED
data/lib/api_keys.rb
CHANGED
@@ -2,22 +2,15 @@
|
|
2
2
|
|
3
3
|
# This is the entry point to the gem.
|
4
4
|
|
5
|
+
# Global requires that don't depend on ApiKeys module structure
|
5
6
|
require "rails"
|
6
7
|
require "active_record"
|
7
|
-
require "active_support/all"
|
8
|
+
require "active_support/all" # For ActiveSupport::SecurityUtils, etc. used in Configuration
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
require "api_keys/models/api_key"
|
15
|
-
|
16
|
-
# Rails integration
|
17
|
-
require "api_keys/engine" if defined?(Rails)
|
18
|
-
|
19
|
-
# Main module that serves as the primary interface to the gem.
|
20
|
-
# Most methods here delegate to Configuration, which is the single source of truth for all config in the initializer
|
10
|
+
# --------- Core ApiKeys Module Definition ---------
|
11
|
+
# Define the ApiKeys module itself and its primary interface methods early.
|
12
|
+
# This ensures that ApiKeys.configuration and ApiKeys.configure are available
|
13
|
+
# when other gem files are loaded and potentially use them at load time.
|
21
14
|
module ApiKeys
|
22
15
|
# Custom error classes
|
23
16
|
class Error < StandardError; end
|
@@ -25,24 +18,56 @@ module ApiKeys
|
|
25
18
|
class << self
|
26
19
|
attr_writer :configuration
|
27
20
|
|
21
|
+
# Provides access to the gem's configuration.
|
22
|
+
# Initializes with default settings if not already configured.
|
23
|
+
# Note: ApiKeys::Configuration class must be loaded before this method
|
24
|
+
# is called in a way that triggers instantiation (e.g. first call).
|
28
25
|
def configuration
|
29
|
-
@configuration
|
26
|
+
# Ensure @configuration is initialized with an instance of ApiKeys::Configuration.
|
27
|
+
# The ApiKeys::Configuration class itself is defined in 'api_keys/configuration.rb'.
|
28
|
+
@configuration ||= ::ApiKeys::Configuration.new
|
30
29
|
end
|
31
30
|
|
32
|
-
#
|
31
|
+
# Main configuration block for the gem.
|
32
|
+
# Example:
|
33
|
+
# ApiKeys.configure do |config|
|
34
|
+
# config.token_prefix = "my_app_"
|
35
|
+
# end
|
33
36
|
def configure
|
34
37
|
yield(configuration)
|
35
38
|
end
|
36
39
|
|
37
40
|
# Resets the configuration to its default values.
|
38
|
-
# Useful for testing.
|
41
|
+
# Useful primarily for testing environments.
|
39
42
|
def reset_configuration!
|
40
|
-
@configuration = Configuration.new
|
43
|
+
@configuration = ::ApiKeys::Configuration.new
|
41
44
|
end
|
42
|
-
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
48
|
+
# --------- Gem Component Requires ---------
|
49
|
+
# Order is important here.
|
50
|
+
# 1. Version: Typically first.
|
51
|
+
# 2. Configuration class: Needed by ApiKeys.configuration method.
|
52
|
+
# 3. Other components: Controllers, models, engine, etc., which might use the configuration.
|
53
|
+
|
54
|
+
require "api_keys/version"
|
55
|
+
require "api_keys/configuration" # Defines the ApiKeys::Configuration class
|
56
|
+
|
57
|
+
# Files that might depend on ApiKeys.configuration being available
|
58
|
+
require "api_keys/controller" # This can lead to loading jobs, etc.
|
59
|
+
require "api_keys/models/concerns/has_api_keys"
|
60
|
+
require "api_keys/models/api_key"
|
61
|
+
|
62
|
+
# Rails integration (Engine)
|
63
|
+
# The Engine might also access ApiKeys.configuration during its initialization.
|
64
|
+
require "api_keys/engine" if defined?(Rails)
|
65
|
+
|
66
|
+
# Consider if a Railtie is needed and where its require should go.
|
67
|
+
# If it also needs ApiKeys.configuration, it should be after the main module definition
|
68
|
+
# and after api_keys/configuration.
|
69
|
+
# require "api_keys/railtie" if defined?(Rails::Railtie)
|
70
|
+
|
46
71
|
# TODO: Require other necessary files like configuration, engine, etc.
|
47
72
|
# require_relative "api_keys/configuration"
|
48
73
|
# require_relative "api_keys/engine"
|
@@ -46,8 +46,19 @@ module ApiKeys
|
|
46
46
|
say " end"
|
47
47
|
say " # ..."
|
48
48
|
say " end"
|
49
|
-
say "\n 3.
|
50
|
-
say "
|
49
|
+
say "\n 3. IMPORTANT: If API keys belong to a model other than User (e.g., Organization),"
|
50
|
+
say " configure the owner context in `config/initializers/api_keys.rb`:", :yellow
|
51
|
+
say " # For Organization-owned API keys:"
|
52
|
+
say " config.current_owner_method = :current_organization"
|
53
|
+
say " config.authenticate_owner_method = :authenticate_organization!"
|
54
|
+
say "\n The dashboard requires these methods to exist in your ApplicationController"
|
55
|
+
say " or wherever you mount the engine. They should:"
|
56
|
+
say " - `current_owner_method`: return the logged-in owner (e.g., current_organization)"
|
57
|
+
say " - `authenticate_owner_method`: ensure the owner is authenticated"
|
58
|
+
say "\n 4. Mount the API keys dashboard in your `routes.rb` to provide a self-serve interface:"
|
59
|
+
say " # In config/routes.rb"
|
60
|
+
say " mount ApiKeys::Engine => '/settings/api-keys'"
|
61
|
+
say "\n 5. In your app's API controllers, verify API keys by including `ApiKeys::Controller`:"
|
51
62
|
say " # Example for app/controllers/api/base_controller.rb"
|
52
63
|
say " class Api::BaseController < ActionController::API"
|
53
64
|
say " include ApiKeys::Controller"
|
@@ -38,6 +38,30 @@ ApiKeys.configure do |config|
|
|
38
38
|
# Default: :sha256
|
39
39
|
# config.hash_strategy = :bcrypt
|
40
40
|
|
41
|
+
# === Dashboard Configuration ===
|
42
|
+
|
43
|
+
# IMPORTANT: Owner Context Configuration
|
44
|
+
# The api_keys dashboard needs to know how to find the current owner
|
45
|
+
# of API keys in your application. By default, it assumes you have
|
46
|
+
# a User model with current_user/authenticate_user! methods (Devise-style).
|
47
|
+
#
|
48
|
+
# If your API keys belong to a different model (e.g., Organization),
|
49
|
+
# you MUST configure these methods:
|
50
|
+
|
51
|
+
# The method that returns the current API key owner in your controllers.
|
52
|
+
# This should return the object that has_api_keys (e.g., current_organization).
|
53
|
+
# Default: :current_user
|
54
|
+
# config.current_owner_method = :current_organization
|
55
|
+
|
56
|
+
# The method that authenticates/requires login for the dashboard.
|
57
|
+
# This should ensure the owner is logged in before accessing the dashboard.
|
58
|
+
# Default: :authenticate_user!
|
59
|
+
# config.authenticate_owner_method = :authenticate_organization!
|
60
|
+
|
61
|
+
# Example for Organization-owned keys:
|
62
|
+
# config.current_owner_method = :current_organization
|
63
|
+
# config.authenticate_owner_method = :authenticate_organization!
|
64
|
+
|
41
65
|
# === Optional Behaviors ===
|
42
66
|
|
43
67
|
# Global limit on the number of *active* keys an owner can have.
|
@@ -70,7 +94,7 @@ ApiKeys.configure do |config|
|
|
70
94
|
# Set to 0 or nil to disable caching.
|
71
95
|
# Uses Rails.cache.
|
72
96
|
# Default: 5.seconds
|
73
|
-
# config.cache_ttl = 30.seconds # Higher TTL = higher risk of
|
97
|
+
# config.cache_ttl = 30.seconds # Higher TTL = higher risk of "revoked-but-still-valid" edge cases
|
74
98
|
|
75
99
|
# === Security ===
|
76
100
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_keys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rameerez
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-06-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|