super_auth 0.3.3 → 0.7.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +8 -1
  5. data/README.md +286 -22
  6. data/USAGE.md +41 -16
  7. data/config/routes.rb +9 -71
  8. data/db/migrate/10_add_super_auth_label_to_resources.rb +13 -0
  9. data/db/migrate/1_users.rb +1 -1
  10. data/db/migrate/5_resources.rb +1 -1
  11. data/db/migrate/7_authorization.rb +2 -2
  12. data/db/migrate/8_add_indexes_to_edges.rb +7 -0
  13. data/db/migrate_activerecord/20250101000001_create_super_auth_users.rb +1 -1
  14. data/db/migrate_activerecord/20250101000005_create_super_auth_resources.rb +1 -1
  15. data/db/migrate_activerecord/20250101000007_create_super_auth_authorizations.rb +2 -2
  16. data/db/migrate_activerecord/20250101000010_add_super_auth_label_to_super_auth_resources.rb +5 -0
  17. data/exe/super_auth-editor +9 -0
  18. data/lib/generators/super_auth/install/templates/README +15 -10
  19. data/lib/generators/super_auth/install/templates/super_auth.rb +16 -5
  20. data/lib/generators/super_auth/rls/rls_generator.rb +22 -0
  21. data/lib/generators/super_auth/rls/templates/migration.rb.erb +15 -0
  22. data/lib/super_auth/active_record/by_current_user.rb +25 -5
  23. data/lib/super_auth/active_record/resource.rb +41 -0
  24. data/lib/super_auth/active_record/user.rb +3 -1
  25. data/lib/super_auth/authorization.rb +12 -0
  26. data/lib/super_auth/edge.rb +65 -100
  27. data/lib/super_auth/editor/cli.rb +91 -0
  28. data/lib/super_auth/editor/index.html +423 -0
  29. data/lib/super_auth/editor/seed.rb +170 -0
  30. data/lib/super_auth/editor.rb +273 -0
  31. data/lib/super_auth/nestable.rb +41 -3
  32. data/lib/super_auth/railtie.rb +0 -2
  33. data/lib/super_auth/rls.rb +256 -0
  34. data/lib/super_auth/user.rb +3 -1
  35. data/lib/super_auth/version.rb +1 -1
  36. data/lib/super_auth.rb +85 -0
  37. data/lib/tasks/super_auth_tasks.rake +28 -0
  38. metadata +15 -9
  39. data/VISUALIZATION.md +0 -58
  40. data/app/controllers/super_auth/graph_controller.rb +0 -654
  41. data/app/views/super_auth/graph/index.html.erb +0 -1408
  42. data/super_auth.gemspec +0 -35
  43. data/visualization.html +0 -747
@@ -8,6 +8,11 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
8
8
  many_to_one :resource
9
9
 
10
10
  class << self
11
+ # The five strategies are UNIONed positionally. A column that is a real
12
+ # text column in one strategy and CAST(NULL AS ...) in another must be cast
13
+ # in every strategy: on MySQL the table collation and the connection
14
+ # collation can differ, and a column meeting a NULL cast at the same
15
+ # coercibility raises "Illegal mix of collations for operation 'UNION'".
11
16
  def string_cast_type
12
17
  case SuperAuth.db.database_type
13
18
  when :mysql, :mysql2
@@ -36,44 +41,24 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
36
41
 
37
42
  def users_groups_roles_permissions_resources
38
43
  cast_type = string_cast_type
39
- users_groups_roles_ds = SuperAuth::User.join(:super_auth_edges, user_id: :id).
40
- select_all(:super_auth_users).
41
- join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:groups), Sequel.function(:concat, ',', Sequel[:groups][:group_path], ',').like(Sequel.function(:concat, '%,', Sequel[:groups][:id], ',%'))).
42
- select(
43
- Sequel[:super_auth_users][:id].as(:user_id),
44
- Sequel[:super_auth_users][:name].as(:user_name),
45
- Sequel[:super_auth_users][:external_id].as(:user_external_id),
46
- Sequel[:super_auth_users][:external_type].as(:user_external_type),
47
- Sequel[:super_auth_users][:created_at].as(:user_created_at),
48
- Sequel[:super_auth_users][:updated_at].as(:user_updated_at),
49
- Sequel[:groups][:id].as(:group_id),
50
- Sequel[:groups][:name].as(:group_name),
51
- Sequel[:super_auth_edges][:id].as(:edge_id),
52
- Sequel[:super_auth_edges][:permission_id].as(:edge_permission_id),
53
- Sequel[:super_auth_edges][:group_id].as(:edge_group_id),
54
- Sequel[:super_auth_edges][:user_id].as(:edge_user_id),
55
- Sequel[:super_auth_edges][:role_id].as(:edge_role_id),
56
- Sequel[:groups][:group_path],
57
- Sequel[:groups][:group_name_path],
58
- Sequel[:groups][:parent_id],
59
- Sequel[:groups][:created_at].cast(cast_type).as(:group_created_at),
60
- Sequel[:groups][:updated_at].cast(cast_type).as(:group_updated_at),
61
- ).join(Sequel[:super_auth_edges].as(:group_role_edges), Sequel[:group_role_edges][:group_id] => Sequel[:groups][:id]).select_append(
62
- Sequel[:group_role_edges][:id].as(:group_role_edge_id),
63
- Sequel[:group_role_edges][:permission_id].as(:group_role_edge_permission_id),
64
- Sequel[:group_role_edges][:group_id].as(:group_role_edge_group_id),
65
- Sequel[:group_role_edges][:user_id].as(:group_role_edge_user_id),
66
- Sequel[:group_role_edges][:role_id].as(:group_role_edge_role_id),
67
- ).join(:super_auth_roles, id: Sequel[:group_role_edges][:role_id])
68
-
69
- SuperAuth::Edge.from(
70
- SuperAuth::Edge.from(
71
- SuperAuth::Group.cte(SuperAuth::Group.where(id: users_groups_roles_ds.select(Sequel[:groups][:id])).select(:id)).select { [id.as(:group_id), name.as(:group_name), parent_id.as(:group_parent_id), group_path, group_name_path, created_at.cast(cast_type).as(:group_created_at), updated_at.as(:group_updated_at)] },
72
- SuperAuth::Role.cte(users_groups_roles_ds.select(Sequel[:group_role_edges][:role_id])).select { [id.as(:role_id), name.as(:role_name), parent_id.as(:role_parent_id), role_path, role_name_path, created_at.as(:role_created_at), updated_at.as(:role_updated_at) ] }
73
- ).as(:users_groups_roles_permissions_resources)
74
- ).join(Sequel[:super_auth_edges].as(:user_edges), Sequel[:user_edges][:group_id] => Sequel[:users_groups_roles_permissions_resources][:group_id])
75
- .join(Sequel[:super_auth_users], id: Sequel[:user_edges][:user_id])
76
- .select(
44
+ # Join users to their group via edges. group_ancestors pairs that group with itself and
45
+ # every ancestor, so a group -> role edge on any of them applies. role_descendants then
46
+ # expands the granted role to its whole subtree. Each step is correlated to the previous
47
+ # one, so a role held by one group never reaches members of an unrelated group. The tree
48
+ # CTEs (user_groups, granted_roles) are joined by id only to supply the path columns.
49
+ SuperAuth::User.db[:super_auth_users].
50
+ join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
51
+ join(SuperAuth::Group.ancestor_pairs.as(:group_ancestors), descendant_id: Sequel[:user_edges][:group_id]).
52
+ join(Sequel[:super_auth_edges].as(:group_role_edges), group_id: Sequel[:group_ancestors][:ancestor_id]).
53
+ where(Sequel.~(Sequel[:group_role_edges][:role_id] => nil)).
54
+ join(SuperAuth::Role.descendant_pairs.as(:role_descendants), ancestor_id: Sequel[:group_role_edges][:role_id]).
55
+ join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:user_groups), Sequel[:user_groups][:id] => Sequel[:user_edges][:group_id]).
56
+ join(SuperAuth::Role.from(SuperAuth::Role.trees).as(:granted_roles), Sequel[:granted_roles][:id] => Sequel[:role_descendants][:descendant_id]).
57
+ join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:granted_roles][:id]).
58
+ join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id]).
59
+ join(Sequel[:super_auth_edges].as(:resource_edges), Sequel[:resource_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
60
+ join(Sequel[:super_auth_resources], id: Sequel[:resource_edges][:resource_id]).
61
+ select(
77
62
  Sequel[:super_auth_users][:id].as(:user_id),
78
63
  Sequel[:super_auth_users][:name].as(:user_name),
79
64
  Sequel[:super_auth_users][:external_id].as(:user_external_id),
@@ -81,24 +66,24 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
81
66
  Sequel[:super_auth_users][:created_at].cast(cast_type).as(:user_created_at),
82
67
  Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
83
68
 
84
- Sequel[:users_groups_roles_permissions_resources][:group_id],
85
- Sequel[:users_groups_roles_permissions_resources][:group_name],
86
- Sequel[:users_groups_roles_permissions_resources][:group_path],
87
- Sequel[:users_groups_roles_permissions_resources][:group_name_path],
88
- Sequel[:users_groups_roles_permissions_resources][:group_parent_id],
89
- Sequel[:users_groups_roles_permissions_resources][:group_created_at].cast(cast_type).as(:group_created_at),
90
- Sequel[:users_groups_roles_permissions_resources][:group_updated_at].cast(cast_type).as(:group_updated_at),
69
+ Sequel[:user_groups][:id].as(:group_id),
70
+ Sequel[:user_groups][:name].cast(cast_type).as(:group_name),
71
+ Sequel[:user_groups][:group_path],
72
+ Sequel[:user_groups][:group_name_path].cast(cast_type).as(:group_name_path),
73
+ Sequel[:user_groups][:parent_id].as(:group_parent_id),
74
+ Sequel[:user_groups][:created_at].cast(cast_type).as(:group_created_at),
75
+ Sequel[:user_groups][:updated_at].cast(cast_type).as(:group_updated_at),
91
76
 
92
- Sequel[:users_groups_roles_permissions_resources][:role_id],
93
- Sequel[:users_groups_roles_permissions_resources][:role_name],
94
- Sequel[:users_groups_roles_permissions_resources][:role_path],
95
- Sequel[:users_groups_roles_permissions_resources][:role_name_path],
96
- Sequel[:users_groups_roles_permissions_resources][:role_parent_id],
97
- Sequel[:users_groups_roles_permissions_resources][:role_created_at].cast(cast_type).as(:role_created_at),
98
- Sequel[:users_groups_roles_permissions_resources][:role_updated_at].cast(cast_type).as(:role_updated_at),
77
+ Sequel[:granted_roles][:id].as(:role_id),
78
+ Sequel[:granted_roles][:name].cast(cast_type).as(:role_name),
79
+ Sequel[:granted_roles][:role_path],
80
+ Sequel[:granted_roles][:role_name_path].cast(cast_type).as(:role_name_path),
81
+ Sequel[:granted_roles][:parent_id].as(:role_parent_id),
82
+ Sequel[:granted_roles][:created_at].cast(cast_type).as(:role_created_at),
83
+ Sequel[:granted_roles][:updated_at].cast(cast_type).as(:role_updated_at),
99
84
 
100
85
  Sequel[:super_auth_permissions][:id].as(:permission_id),
101
- Sequel[:super_auth_permissions][:name].as(:permission_name),
86
+ Sequel[:super_auth_permissions][:name].cast(cast_type).as(:permission_name),
102
87
  Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
103
88
  Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
104
89
 
@@ -106,27 +91,20 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
106
91
  Sequel[:super_auth_resources][:name].as(:resource_name),
107
92
  Sequel[:super_auth_resources][:external_id].as(:resource_external_id),
108
93
  Sequel[:super_auth_resources][:external_type].as(:resource_external_type)
109
- )
110
- .join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:users_groups_roles_permissions_resources][:role_id])
111
- .join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id])
112
- .join(Sequel[:super_auth_edges].as(:resource_edges), Sequel[:resource_edges][:permission_id] => Sequel[:permission_edges][:permission_id])
113
- .join(Sequel[:super_auth_resources], id: Sequel[:resource_edges][:resource_id])
114
- .distinct
94
+ ).
95
+ distinct
115
96
  end
116
97
 
117
98
  def users_groups_permissions_resources
118
99
  cast_type = string_cast_type
119
- # Join users to their group via edges, then to the group CTE to get the user's group_path.
120
- # Use group_path to find all ancestor groups (any group whose id appears in the user's group_path).
121
- # Then join permission edges on those ancestor groups.
100
+ # Join users to their group via edges. group_ancestors pairs that group with itself and
101
+ # every ancestor, so a group -> permission edge on any of them applies. user_groups (the
102
+ # tree) is joined by id only to supply the path columns.
122
103
  SuperAuth::User.db[:super_auth_users].
123
104
  join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
105
+ join(SuperAuth::Group.ancestor_pairs.as(:group_ancestors), descendant_id: Sequel[:user_edges][:group_id]).
106
+ join(Sequel[:super_auth_edges].as(:group_edges), group_id: Sequel[:group_ancestors][:ancestor_id]).
124
107
  join(SuperAuth::Group.from(SuperAuth::Group.trees).as(:user_groups), Sequel[:user_groups][:id] => Sequel[:user_edges][:group_id]).
125
- join(Sequel[:super_auth_edges].as(:group_edges),
126
- Sequel.function(:concat, ',', Sequel[:user_groups][:group_path], ',').like(
127
- Sequel.function(:concat, '%,', Sequel[:group_edges][:group_id].cast(cast_type), ',%')
128
- )
129
- ).
130
108
  join(Sequel[:super_auth_permissions], id: Sequel[:group_edges][:permission_id]).
131
109
  join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
132
110
  join(Sequel[:super_auth_resources], id: Sequel[:permission_edges][:resource_id]).
@@ -139,9 +117,9 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
139
117
  Sequel[:super_auth_users][:updated_at].cast(cast_type).as(:user_updated_at),
140
118
 
141
119
  Sequel[:user_groups][:id].as(:group_id),
142
- Sequel[:user_groups][:name].as(:group_name),
120
+ Sequel[:user_groups][:name].cast(cast_type).as(:group_name),
143
121
  Sequel[:user_groups][:group_path],
144
- Sequel[:user_groups][:group_name_path],
122
+ Sequel[:user_groups][:group_name_path].cast(cast_type).as(:group_name_path),
145
123
  Sequel[:user_groups][:parent_id].as(:group_parent_id),
146
124
  Sequel[:user_groups][:created_at].cast(cast_type).as(:group_created_at),
147
125
  Sequel[:user_groups][:updated_at].cast(cast_type).as(:group_updated_at),
@@ -155,7 +133,7 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
155
133
  Sequel.cast(nil, string_cast_type).as(:role_updated_at),
156
134
 
157
135
  Sequel[:super_auth_permissions][:id].as(:permission_id),
158
- Sequel[:super_auth_permissions][:name].as(:permission_name),
136
+ Sequel[:super_auth_permissions][:name].cast(cast_type).as(:permission_name),
159
137
  Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
160
138
  Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
161
139
 
@@ -170,26 +148,13 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
170
148
  def users_roles_permissions_resources
171
149
  cast_type = string_cast_type
172
150
 
173
- # Step 1: Find which roles users are directly linked to via edges
174
- user_role_ids_ds = SuperAuth::Edge.where(Sequel.~(user_id: nil) & Sequel.~(role_id: nil)).select(:role_id)
175
-
176
- # Step 2: Expand those roles to all descendants via CTE
177
- role_cte = SuperAuth::Role.cte(user_role_ids_ds).select {
178
- [id.as(:role_id), name.as(:role_name), parent_id.as(:role_parent_id), role_path, role_name_path, created_at.as(:role_created_at), updated_at.as(:role_updated_at)]
179
- }
180
-
181
- # Step 3: Build the query from the expanded role tree
182
- SuperAuth::Edge.from(role_cte.as(:users_roles_permissions_resources)).
183
- # Join user_edges — match users who link to any role in the expanded CTE
184
- # The user's edge links to an ancestor role, but the CTE path contains that ancestor
185
- # We use the role_path to check: the role_path of the CTE row starts with the user's linked role
186
- join(Sequel[:super_auth_edges].as(:user_edges),
187
- Sequel.function(:concat, ',', Sequel[:users_roles_permissions_resources][:role_path], ',').like(
188
- Sequel.function(:concat, '%,', Sequel[:user_edges][:role_id].cast(cast_type), ',%')
189
- )
190
- ).
191
- where(Sequel.~(Sequel[:user_edges][:user_id] => nil) & Sequel.~(Sequel[:user_edges][:role_id] => nil)).
192
- join(Sequel[:super_auth_users], id: Sequel[:user_edges][:user_id]).
151
+ # Join users to the roles they hold directly. role_descendants expands each held role to
152
+ # its whole subtree; granted_roles (the tree) is joined by id only to supply the path columns.
153
+ SuperAuth::User.db[:super_auth_users].
154
+ join(Sequel[:super_auth_edges].as(:user_edges), user_id: :id).
155
+ where(Sequel.~(Sequel[:user_edges][:role_id] => nil)).
156
+ join(SuperAuth::Role.descendant_pairs.as(:role_descendants), ancestor_id: Sequel[:user_edges][:role_id]).
157
+ join(SuperAuth::Role.from(SuperAuth::Role.trees).as(:granted_roles), Sequel[:granted_roles][:id] => Sequel[:role_descendants][:descendant_id]).
193
158
  select(
194
159
  Sequel[:super_auth_users][:id].as(:user_id),
195
160
  Sequel[:super_auth_users][:name].as(:user_name),
@@ -206,16 +171,16 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
206
171
  Sequel.cast(nil, string_cast_type).as(:group_created_at),
207
172
  Sequel.cast(nil, string_cast_type).as(:group_updated_at),
208
173
 
209
- Sequel[:users_roles_permissions_resources][:role_id],
210
- Sequel[:users_roles_permissions_resources][:role_name],
211
- Sequel[:users_roles_permissions_resources][:role_path],
212
- Sequel[:users_roles_permissions_resources][:role_name_path],
213
- Sequel[:users_roles_permissions_resources][:role_parent_id],
214
- Sequel[:users_roles_permissions_resources][:role_created_at].cast(cast_type).as(:role_created_at),
215
- Sequel[:users_roles_permissions_resources][:role_updated_at].cast(cast_type).as(:role_updated_at),
174
+ Sequel[:granted_roles][:id].as(:role_id),
175
+ Sequel[:granted_roles][:name].cast(cast_type).as(:role_name),
176
+ Sequel[:granted_roles][:role_path],
177
+ Sequel[:granted_roles][:role_name_path].cast(cast_type).as(:role_name_path),
178
+ Sequel[:granted_roles][:parent_id].as(:role_parent_id),
179
+ Sequel[:granted_roles][:created_at].cast(cast_type).as(:role_created_at),
180
+ Sequel[:granted_roles][:updated_at].cast(cast_type).as(:role_updated_at),
216
181
 
217
182
  Sequel[:super_auth_permissions][:id].as(:permission_id),
218
- Sequel[:super_auth_permissions][:name].as(:permission_name),
183
+ Sequel[:super_auth_permissions][:name].cast(cast_type).as(:permission_name),
219
184
  Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
220
185
  Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
221
186
 
@@ -225,7 +190,7 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
225
190
  Sequel[:super_auth_resources][:external_type].as(:resource_external_type),
226
191
  ).
227
192
  # Join permission and resource edges on the expanded role
228
- join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:users_roles_permissions_resources][:role_id]).
193
+ join(Sequel[:super_auth_edges].as(:permission_edges), Sequel[:permission_edges][:role_id] => Sequel[:granted_roles][:id]).
229
194
  join(Sequel[:super_auth_permissions], id: Sequel[:permission_edges][:permission_id]).
230
195
  join(Sequel[:super_auth_edges].as(:resource_edges), Sequel[:resource_edges][:permission_id] => Sequel[:super_auth_permissions][:id]).
231
196
  join(Sequel[:super_auth_resources], id: Sequel[:resource_edges][:resource_id]).
@@ -261,7 +226,7 @@ class SuperAuth::Edge < Sequel::Model(:super_auth_edges)
261
226
  Sequel.cast(nil, string_cast_type).as(:role_updated_at),
262
227
 
263
228
  Sequel[:super_auth_permissions][:id].as(:permission_id),
264
- Sequel[:super_auth_permissions][:name].as(:permission_name),
229
+ Sequel[:super_auth_permissions][:name].cast(cast_type).as(:permission_name),
265
230
  Sequel[:super_auth_permissions][:created_at].cast(cast_type).as(:permission_created_at),
266
231
  Sequel[:super_auth_permissions][:updated_at].cast(cast_type).as(:permission_updated_at),
267
232
 
@@ -0,0 +1,91 @@
1
+ require "optparse"
2
+ require "super_auth/editor"
3
+
4
+ module SuperAuth
5
+ class Editor
6
+ # Boots the editor from the command line: connect, optionally migrate and
7
+ # seed, then serve on loopback. See exe/super_auth-editor.
8
+ module CLI
9
+ DEFAULTS = { host: "127.0.0.1", port: 4666, migrate: false, seed: false }.freeze
10
+ LOOPBACK_BINDS = %w[127.0.0.1 localhost ::1].freeze
11
+ # Host headers accepted when bound to loopback (DNS-rebinding defence).
12
+ LOOPBACK_HOSTS = %w[localhost 127.0.0.1 [::1]].freeze
13
+ WARNING = "WARNING: the editor has no authentication. " \
14
+ "Anyone who can reach this port can rewrite the authorization graph.".freeze
15
+
16
+ module_function
17
+
18
+ def parse(argv, out: $stdout)
19
+ options = DEFAULTS.dup
20
+ parser = OptionParser.new do |o|
21
+ o.banner = "Usage: super_auth-editor [options]"
22
+ o.on("-H", "--host HOST", "bind address (default #{DEFAULTS[:host]})") { |v| options[:host] = v }
23
+ o.on("-p", "--port PORT", Integer, "port (default #{DEFAULTS[:port]})") { |v| options[:port] = v }
24
+ o.on("--migrate", "run the super_auth Sequel migrations before starting") { options[:migrate] = true }
25
+ o.on("--seed", "replace the whole graph with the Acme Cloud sample (destructive)") { options[:seed] = true }
26
+ o.on("-v", "--version", "print the version") do
27
+ out.puts SuperAuth::VERSION
28
+ exit
29
+ end
30
+ o.on("-h", "--help", "show this help") do
31
+ out.puts o
32
+ out.puts "Reads SUPER_AUTH_DATABASE_URL (required)."
33
+ out.puts WARNING
34
+ exit
35
+ end
36
+ end
37
+ parser.parse!(argv.dup)
38
+ options
39
+ rescue OptionParser::ParseError => e
40
+ raise SuperAuth::Error, "#{e.message}\n#{parser}"
41
+ end
42
+
43
+ def run(argv, env: ENV, err: $stderr)
44
+ options = parse(argv)
45
+ url = env["SUPER_AUTH_DATABASE_URL"].to_s.strip
46
+ if url.empty?
47
+ raise SuperAuth::Error, "SUPER_AUTH_DATABASE_URL is not set. Point it at the database that holds " \
48
+ "the super_auth tables, e.g. postgres://user:password@localhost/app_development"
49
+ end
50
+
51
+ # First Sequel::Database in the process: the models bind to it.
52
+ begin
53
+ SuperAuth.db = Sequel.connect(url)
54
+ rescue Sequel::AdapterNotFound => e
55
+ raise SuperAuth::Error, "#{e.message}. Install the adapter gem for this URL (pg, mysql2 or sqlite3)."
56
+ end
57
+
58
+ SuperAuth.install_migrations if options[:migrate]
59
+ begin
60
+ SuperAuth.load
61
+ rescue Sequel::DatabaseError => e
62
+ raise SuperAuth::Error, "super_auth tables not found at #{redact(url)} (#{e.message.lines.first.to_s.strip}). " \
63
+ "Run with --migrate to create them, or run your application's migrations."
64
+ end
65
+
66
+ if options[:seed]
67
+ require "super_auth/editor/seed"
68
+ counts = SuperAuth::Editor::Seed.run!
69
+ err.puts "Seeded the Acme Cloud sample graph: #{counts.map { |k, v| "#{v} #{k}" }.join(', ')}."
70
+ end
71
+
72
+ begin
73
+ require "rackup"
74
+ rescue LoadError
75
+ raise SuperAuth::Error, "The editor needs a Rack server: gem install rackup webrick (puma also works)."
76
+ end
77
+
78
+ loopback = LOOPBACK_BINDS.include?(options[:host])
79
+ err.puts WARNING
80
+ err.puts "Bound to #{options[:host]}, which other machines can reach." unless loopback
81
+ err.puts "Editor at http://#{options[:host]}:#{options[:port]}"
82
+ app = SuperAuth::Editor.new(hosts: loopback ? LOOPBACK_HOSTS : nil)
83
+ Rackup::Server.start(app: app, Host: options[:host], Port: options[:port], environment: "deployment")
84
+ end
85
+
86
+ def redact(url)
87
+ url.sub(%r{//([^:/@]+):[^@]*@}, '//\1:***@')
88
+ end
89
+ end
90
+ end
91
+ end