hokipoki 0.3.4 ā 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/hive_mind/install_generator.rb +18 -2
- data/lib/generators/hive_mind/start_generator.rb +582 -0
- data/lib/generators/hive_mind/templates/hokipoki_claude.rb +45 -0
- data/lib/generators/hokipoki/attach_parasite_generator.rb +355 -0
- data/lib/generators/hokipoki/install_generator.rb +515 -0
- data/lib/generators/hokipoki/scan_project_generator.rb +279 -0
- data/lib/generators/parasite/install_generator.rb +458 -0
- data/lib/hokipoki/atomic_fact_extractor.rb +524 -0
- data/lib/hokipoki/claude/parasite.rb +62 -10
- data/lib/hokipoki/claude/thought_interceptor.rb +385 -0
- data/lib/hokipoki/claude_auto_loader.rb +28 -11
- data/lib/hokipoki/template_store.rb +425 -0
- data/lib/hokipoki/vector_engine.rb +525 -0
- data/lib/hokipoki/version.rb +1 -1
- data/lib/hokipoki.rb +260 -6
- metadata +81 -1
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/base'
|
|
5
|
+
|
|
6
|
+
module Hokipoki
|
|
7
|
+
module Generators
|
|
8
|
+
# Hokipoki Install Generator - Sets up core gem dependencies and OTP authentication
|
|
9
|
+
# Command: rails g hokipoki:install
|
|
10
|
+
class InstallGenerator < Rails::Generators::Base
|
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
|
12
|
+
|
|
13
|
+
class_option :skip_otp, type: :boolean, default: false,
|
|
14
|
+
desc: 'Skip OTP setup (not recommended for production)'
|
|
15
|
+
|
|
16
|
+
class_option :auth_provider, type: :string, default: 'github',
|
|
17
|
+
desc: 'Authentication provider (github, totp, both)'
|
|
18
|
+
|
|
19
|
+
class_option :force_install, type: :boolean, default: false,
|
|
20
|
+
desc: 'Force installation even if components exist'
|
|
21
|
+
|
|
22
|
+
def display_installation_banner
|
|
23
|
+
say "\nš HOKIPOKI INSTALLATION SYSTEM", :cyan
|
|
24
|
+
say "=" * 60, :cyan
|
|
25
|
+
say "Setting up parasitic intelligence framework...", :white
|
|
26
|
+
say "This will configure authentication, dependencies, and core components.", :yellow
|
|
27
|
+
say ""
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_rails_environment
|
|
31
|
+
unless defined?(Rails)
|
|
32
|
+
say "ā Rails environment required", :red
|
|
33
|
+
say "Please run this generator from a Rails application directory.", :yellow
|
|
34
|
+
exit(1)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
say "ā
Rails detected: #{Rails.version} (#{Rails.env})", :green
|
|
38
|
+
say "š Project: #{Rails.application.class.module_parent_name}", :white
|
|
39
|
+
say ""
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_existing_installation
|
|
43
|
+
if File.exist?('config/initializers/hokipoki.rb') && !options[:force_install]
|
|
44
|
+
say "ā ļø Hokipoki already installed", :yellow
|
|
45
|
+
say "Use --force-install to reinstall", :white
|
|
46
|
+
say ""
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def install_core_dependencies
|
|
52
|
+
say "š¦ INSTALLING CORE DEPENDENCIES:", :cyan
|
|
53
|
+
|
|
54
|
+
# Add required gems to Gemfile
|
|
55
|
+
gems_to_add = [
|
|
56
|
+
{ name: 'rotp', version: '~> 7.0', comment: 'For TOTP generation/verification' },
|
|
57
|
+
{ name: 'rqrcode', version: '~> 2.2', comment: 'For QR codes' },
|
|
58
|
+
{ name: 'sqlite3', version: '~> 1.4', comment: 'For vector database' },
|
|
59
|
+
{ name: 'pastel', version: '~> 0.8', comment: 'For colored console output' }
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
if options[:auth_provider].include?('github')
|
|
63
|
+
gems_to_add << { name: 'omniauth-github', version: '~> 2.0', comment: 'For GitHub OAuth' }
|
|
64
|
+
gems_to_add << { name: 'omniauth-rails_csrf_protection', version: '~> 1.0', comment: 'CSRF protection for OAuth' }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
add_gems_to_gemfile(gems_to_add)
|
|
68
|
+
|
|
69
|
+
say " ā
Dependencies added to Gemfile", :green
|
|
70
|
+
say " š Run: bundle install", :yellow
|
|
71
|
+
say ""
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def setup_encryption_keys
|
|
75
|
+
say "š SETTING UP ENCRYPTION:", :cyan
|
|
76
|
+
|
|
77
|
+
# Generate Rails encryption key if not exists
|
|
78
|
+
if !Rails.application.credentials.secret_key_base
|
|
79
|
+
say " š Generating Rails encryption keys...", :white
|
|
80
|
+
|
|
81
|
+
# Create master key if it doesn't exist
|
|
82
|
+
unless File.exist?('config/master.key')
|
|
83
|
+
master_key = SecureRandom.hex(32)
|
|
84
|
+
create_file 'config/master.key', master_key
|
|
85
|
+
say " ā
Created config/master.key", :green
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Set up credentials for OTP
|
|
89
|
+
credentials_content = {
|
|
90
|
+
hokipoki: {
|
|
91
|
+
otp_encryption_key: SecureRandom.hex(32),
|
|
92
|
+
vector_encryption_key: SecureRandom.hex(32)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if options[:auth_provider].include?('github')
|
|
97
|
+
credentials_content[:hokipoki][:github] = {
|
|
98
|
+
client_id: 'your_github_client_id_here',
|
|
99
|
+
client_secret: 'your_github_client_secret_here'
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# We'll prompt user to set up credentials manually
|
|
104
|
+
say " š Credentials setup required - see installation steps below", :yellow
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
say " ā
Encryption configuration ready", :green
|
|
108
|
+
say ""
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def create_hokipoki_initializer
|
|
112
|
+
say "āļø CREATING INITIALIZER:", :cyan
|
|
113
|
+
|
|
114
|
+
create_file "config/initializers/hokipoki.rb", <<~RUBY
|
|
115
|
+
# Hokipoki Configuration
|
|
116
|
+
# Generated by: rails g hokipoki:install
|
|
117
|
+
|
|
118
|
+
Hokipoki.configure do |config|
|
|
119
|
+
# Core settings
|
|
120
|
+
config.enabled = true
|
|
121
|
+
config.environment = Rails.env
|
|
122
|
+
|
|
123
|
+
# Authentication settings
|
|
124
|
+
config.require_otp = #{!options[:skip_otp]}
|
|
125
|
+
config.auth_provider = '#{options[:auth_provider]}'
|
|
126
|
+
|
|
127
|
+
# Vector database settings
|
|
128
|
+
config.vector_storage = :sqlite
|
|
129
|
+
config.vector_db_path = File.expand_path('~/.hokipoki/vectors.db')
|
|
130
|
+
|
|
131
|
+
# Parasite settings
|
|
132
|
+
config.parasite_mode = :auto_detect
|
|
133
|
+
config.claude_detection = :enhanced
|
|
134
|
+
config.visible_output = true
|
|
135
|
+
|
|
136
|
+
# Token budget management
|
|
137
|
+
config.max_context_tokens = 1500
|
|
138
|
+
config.max_response_tokens = 1000
|
|
139
|
+
config.total_token_budget = 3000
|
|
140
|
+
|
|
141
|
+
# Compression settings
|
|
142
|
+
config.template_compression = true
|
|
143
|
+
config.atomic_facts = true
|
|
144
|
+
config.compression_target = 0.75 # 75% reduction
|
|
145
|
+
|
|
146
|
+
# Security settings
|
|
147
|
+
config.encryption_key = Rails.application.credentials.dig(:hokipoki, :otp_encryption_key)
|
|
148
|
+
config.vector_encryption = Rails.application.credentials.dig(:hokipoki, :vector_encryption_key)
|
|
149
|
+
|
|
150
|
+
# GitHub OAuth (if enabled)
|
|
151
|
+
if config.auth_provider.include?('github')
|
|
152
|
+
config.github_client_id = Rails.application.credentials.dig(:hokipoki, :github, :client_id)
|
|
153
|
+
config.github_client_secret = Rails.application.credentials.dig(:hokipoki, :github, :client_secret)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Development settings
|
|
157
|
+
if Rails.env.development?
|
|
158
|
+
config.debug_mode = true
|
|
159
|
+
config.verbose_logging = true
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Auto-load intelligence components
|
|
164
|
+
if Rails.env.development?
|
|
165
|
+
Rails.application.config.after_initialize do
|
|
166
|
+
Thread.new do
|
|
167
|
+
sleep(1) # Let Rails fully initialize
|
|
168
|
+
|
|
169
|
+
begin
|
|
170
|
+
# Display startup banner
|
|
171
|
+
puts "\\nš§ HOKIPOKI: Parasitic intelligence framework loaded"
|
|
172
|
+
puts "š¦ Status: Ready for enhanced Claude interactions"
|
|
173
|
+
|
|
174
|
+
# Auto-detect Claude CLI
|
|
175
|
+
if Hokipoki.claude_parasite_active?
|
|
176
|
+
puts "ā
Claude CLI detected: Enhanced intelligence active"
|
|
177
|
+
puts "šÆ Next: Run 'rails g hive_mind:install' for full setup"
|
|
178
|
+
else
|
|
179
|
+
puts "ā ļø Claude CLI not detected: Run when using Claude"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
puts ""
|
|
183
|
+
rescue => e
|
|
184
|
+
Rails.logger.error "š¦ Hokipoki startup error: \#{e.message}"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
RUBY
|
|
190
|
+
|
|
191
|
+
say " ā
Created config/initializers/hokipoki.rb", :green
|
|
192
|
+
say ""
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def setup_otp_authentication
|
|
196
|
+
return if options[:skip_otp]
|
|
197
|
+
|
|
198
|
+
say "š SETTING UP OTP AUTHENTICATION:", :cyan
|
|
199
|
+
|
|
200
|
+
# Create OTP model
|
|
201
|
+
create_otp_model
|
|
202
|
+
|
|
203
|
+
# Create OTP controller
|
|
204
|
+
create_otp_controller
|
|
205
|
+
|
|
206
|
+
# Add routes
|
|
207
|
+
setup_otp_routes
|
|
208
|
+
|
|
209
|
+
# Create views
|
|
210
|
+
create_otp_views
|
|
211
|
+
|
|
212
|
+
say " ā
OTP authentication configured", :green
|
|
213
|
+
say ""
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def create_vector_storage
|
|
217
|
+
say "š§ SETTING UP VECTOR STORAGE:", :cyan
|
|
218
|
+
|
|
219
|
+
# Create vector storage directory
|
|
220
|
+
vector_dir = File.expand_path('~/.hokipoki')
|
|
221
|
+
FileUtils.mkdir_p(vector_dir) unless Dir.exist?(vector_dir)
|
|
222
|
+
|
|
223
|
+
# Create initial configuration
|
|
224
|
+
config_file = File.join(vector_dir, 'config.yml')
|
|
225
|
+
config_data = {
|
|
226
|
+
version: Hokipoki::VERSION,
|
|
227
|
+
installation_date: Time.current.iso8601,
|
|
228
|
+
project_name: Rails.application.class.module_parent_name,
|
|
229
|
+
project_path: Rails.root.to_s,
|
|
230
|
+
auth_provider: options[:auth_provider],
|
|
231
|
+
otp_enabled: !options[:skip_otp]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
File.write(config_file, config_data.to_yaml)
|
|
235
|
+
|
|
236
|
+
say " ā
Vector storage directory: #{vector_dir}", :green
|
|
237
|
+
say " š Configuration: #{config_file}", :white
|
|
238
|
+
say ""
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def display_next_steps
|
|
242
|
+
say "š HOKIPOKI CORE INSTALLATION COMPLETE!", :green
|
|
243
|
+
say "=" * 60, :green
|
|
244
|
+
say ""
|
|
245
|
+
say "š NEXT STEPS:", :cyan
|
|
246
|
+
say ""
|
|
247
|
+
say "1. š¦ Install dependencies:", :white
|
|
248
|
+
say " bundle install", :yellow
|
|
249
|
+
say ""
|
|
250
|
+
say "2. š Configure credentials (REQUIRED):", :white
|
|
251
|
+
say " EDITOR=nano rails credentials:edit", :yellow
|
|
252
|
+
say ""
|
|
253
|
+
say " Add this structure:", :white
|
|
254
|
+
say credentials_template, :yellow
|
|
255
|
+
say ""
|
|
256
|
+
say "3. šļø Run database migrations:", :white
|
|
257
|
+
say " rails db:migrate", :yellow
|
|
258
|
+
say ""
|
|
259
|
+
say "4. š§ Install HiveMind intelligence:", :white
|
|
260
|
+
say " rails g hive_mind:install", :yellow
|
|
261
|
+
say ""
|
|
262
|
+
say "5. š¦ Attach parasite (after HiveMind):", :white
|
|
263
|
+
say " rails g hokipoki:attach_parasite", :yellow
|
|
264
|
+
say ""
|
|
265
|
+
say "š§ TROUBLESHOOTING:", :cyan
|
|
266
|
+
say " - If bundle fails: Check Ruby version compatibility", :white
|
|
267
|
+
say " - If credentials fail: Ensure master.key exists", :white
|
|
268
|
+
say " - If GitHub OAuth needed: Set up GitHub app first", :white
|
|
269
|
+
say ""
|
|
270
|
+
say "š DOCUMENTATION:", :cyan
|
|
271
|
+
say " - GitHub OAuth setup: https://docs.github.com/en/apps/oauth-apps", :white
|
|
272
|
+
say " - Rails credentials: https://guides.rubyonrails.org/security.html#custom-credentials", :white
|
|
273
|
+
say ""
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
private
|
|
277
|
+
|
|
278
|
+
def add_gems_to_gemfile(gems)
|
|
279
|
+
gemfile_path = 'Gemfile'
|
|
280
|
+
|
|
281
|
+
gems.each do |gem_info|
|
|
282
|
+
gem_line = "gem '#{gem_info[:name]}', '#{gem_info[:version]}' # #{gem_info[:comment]}"
|
|
283
|
+
|
|
284
|
+
# Check if gem already exists
|
|
285
|
+
if File.read(gemfile_path).include?("gem '#{gem_info[:name]}'")
|
|
286
|
+
say " āļø #{gem_info[:name]} already in Gemfile", :yellow
|
|
287
|
+
else
|
|
288
|
+
append_to_file gemfile_path, "\n#{gem_line}"
|
|
289
|
+
say " ā
Added #{gem_info[:name]} #{gem_info[:version]}", :green
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def create_otp_model
|
|
295
|
+
migration_name = "CreateHokipokiOtpUsers"
|
|
296
|
+
migration_file = "db/migrate/#{timestamp}_create_hokipoki_otp_users.rb"
|
|
297
|
+
|
|
298
|
+
create_file migration_file, <<~RUBY
|
|
299
|
+
class CreateHokipokiOtpUsers < ActiveRecord::Migration[7.0]
|
|
300
|
+
def change
|
|
301
|
+
create_table :hokipoki_otp_users do |t|
|
|
302
|
+
t.string :email, null: false
|
|
303
|
+
t.string :encrypted_otp_secret
|
|
304
|
+
t.boolean :otp_enabled, default: false
|
|
305
|
+
t.string :backup_codes, array: true, default: []
|
|
306
|
+
t.datetime :last_otp_at
|
|
307
|
+
t.integer :failed_attempts, default: 0
|
|
308
|
+
t.datetime :locked_until
|
|
309
|
+
|
|
310
|
+
t.timestamps
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
add_index :hokipoki_otp_users, :email, unique: true
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
RUBY
|
|
317
|
+
|
|
318
|
+
# Create model file
|
|
319
|
+
create_file "app/models/hokipoki_otp_user.rb", <<~RUBY
|
|
320
|
+
class HokipokiOtpUser < ApplicationRecord
|
|
321
|
+
validates :email, presence: true, uniqueness: true
|
|
322
|
+
|
|
323
|
+
def generate_otp_secret
|
|
324
|
+
self.encrypted_otp_secret = encrypt_otp_secret(ROTP::Base32.random)
|
|
325
|
+
save!
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def otp_code
|
|
329
|
+
return nil unless encrypted_otp_secret
|
|
330
|
+
|
|
331
|
+
secret = decrypt_otp_secret(encrypted_otp_secret)
|
|
332
|
+
ROTP::TOTP.new(secret)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def verify_otp(code)
|
|
336
|
+
return false unless otp_enabled? && otp_code
|
|
337
|
+
|
|
338
|
+
if otp_code.verify(code, drift: 30)
|
|
339
|
+
update!(last_otp_at: Time.current, failed_attempts: 0)
|
|
340
|
+
true
|
|
341
|
+
else
|
|
342
|
+
increment!(:failed_attempts)
|
|
343
|
+
false
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def qr_code_uri(issuer = 'Hokipoki')
|
|
348
|
+
return nil unless encrypted_otp_secret
|
|
349
|
+
|
|
350
|
+
secret = decrypt_otp_secret(encrypted_otp_secret)
|
|
351
|
+
ROTP::TOTP.new(secret).provisioning_uri(email, issuer_name: issuer)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def generate_backup_codes
|
|
355
|
+
codes = 8.times.map { SecureRandom.hex(4).upcase }
|
|
356
|
+
self.backup_codes = codes
|
|
357
|
+
save!
|
|
358
|
+
codes
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private
|
|
362
|
+
|
|
363
|
+
def encrypt_otp_secret(secret)
|
|
364
|
+
key = Hokipoki.config.encryption_key
|
|
365
|
+
crypt = ActiveSupport::MessageEncryptor.new(key[0..31])
|
|
366
|
+
crypt.encrypt_and_sign(secret)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def decrypt_otp_secret(encrypted_secret)
|
|
370
|
+
key = Hokipoki.config.encryption_key
|
|
371
|
+
crypt = ActiveSupport::MessageEncryptor.new(key[0..31])
|
|
372
|
+
crypt.decrypt_and_verify(encrypted_secret)
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
RUBY
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def create_otp_controller
|
|
379
|
+
create_file "app/controllers/hokipoki/otp_controller.rb", <<~RUBY
|
|
380
|
+
class Hokipoki::OtpController < ApplicationController
|
|
381
|
+
before_action :authenticate_user!
|
|
382
|
+
before_action :find_otp_user
|
|
383
|
+
|
|
384
|
+
def show
|
|
385
|
+
if @otp_user.otp_enabled?
|
|
386
|
+
redirect_to hokipoki_otp_verify_path
|
|
387
|
+
else
|
|
388
|
+
@otp_user.generate_otp_secret unless @otp_user.encrypted_otp_secret
|
|
389
|
+
@qr_code = generate_qr_code(@otp_user.qr_code_uri)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def enable
|
|
394
|
+
if @otp_user.verify_otp(params[:otp_code])
|
|
395
|
+
@otp_user.update!(otp_enabled: true)
|
|
396
|
+
@backup_codes = @otp_user.generate_backup_codes
|
|
397
|
+
flash[:notice] = 'Two-factor authentication enabled successfully!'
|
|
398
|
+
render :backup_codes
|
|
399
|
+
else
|
|
400
|
+
flash.now[:alert] = 'Invalid verification code'
|
|
401
|
+
render :show
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def verify
|
|
406
|
+
# Verification logic for enabled OTP
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def disable
|
|
410
|
+
if @otp_user.verify_otp(params[:otp_code])
|
|
411
|
+
@otp_user.update!(otp_enabled: false, encrypted_otp_secret: nil)
|
|
412
|
+
flash[:notice] = 'Two-factor authentication disabled'
|
|
413
|
+
redirect_to root_path
|
|
414
|
+
else
|
|
415
|
+
flash.now[:alert] = 'Invalid verification code'
|
|
416
|
+
render :verify
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
private
|
|
421
|
+
|
|
422
|
+
def find_otp_user
|
|
423
|
+
@otp_user = HokipokiOtpUser.find_or_create_by(email: current_user.email)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def generate_qr_code(uri)
|
|
427
|
+
qr = RQRCode::QRCode.new(uri)
|
|
428
|
+
qr.as_svg(module_size: 4)
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
RUBY
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def setup_otp_routes
|
|
435
|
+
route_content = <<~RUBY
|
|
436
|
+
|
|
437
|
+
# Hokipoki OTP routes
|
|
438
|
+
namespace :hokipoki do
|
|
439
|
+
resource :otp, only: [:show] do
|
|
440
|
+
post :enable
|
|
441
|
+
get :verify
|
|
442
|
+
delete :disable
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
RUBY
|
|
446
|
+
|
|
447
|
+
append_to_file 'config/routes.rb', route_content
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def create_otp_views
|
|
451
|
+
# Create views directory
|
|
452
|
+
empty_directory "app/views/hokipoki"
|
|
453
|
+
empty_directory "app/views/hokipoki/otp"
|
|
454
|
+
|
|
455
|
+
# Show view (setup)
|
|
456
|
+
create_file "app/views/hokipoki/otp/show.html.erb", <<~ERB
|
|
457
|
+
<div class="hokipoki-otp-setup">
|
|
458
|
+
<h2>š Set Up Two-Factor Authentication</h2>
|
|
459
|
+
|
|
460
|
+
<div class="setup-instructions">
|
|
461
|
+
<h3>Step 1: Install an authenticator app</h3>
|
|
462
|
+
<p>Download an app like Google Authenticator, Authy, or 1Password on your phone.</p>
|
|
463
|
+
|
|
464
|
+
<h3>Step 2: Scan this QR code</h3>
|
|
465
|
+
<div class="qr-code">
|
|
466
|
+
<%= @qr_code.html_safe %>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
<h3>Step 3: Enter the 6-digit code</h3>
|
|
470
|
+
<%= form_with url: hokipoki_otp_enable_path, method: :post do |form| %>
|
|
471
|
+
<%= form.text_field :otp_code, placeholder: "000000", maxlength: 6, required: true %>
|
|
472
|
+
<%= form.submit "Enable Two-Factor Authentication", class: "btn btn-primary" %>
|
|
473
|
+
<% end %>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
ERB
|
|
477
|
+
|
|
478
|
+
# Backup codes view
|
|
479
|
+
create_file "app/views/hokipoki/otp/backup_codes.html.erb", <<~ERB
|
|
480
|
+
<div class="hokipoki-backup-codes">
|
|
481
|
+
<h2>š Your Backup Codes</h2>
|
|
482
|
+
|
|
483
|
+
<div class="alert alert-warning">
|
|
484
|
+
<strong>Important:</strong> Save these backup codes in a safe place.
|
|
485
|
+
You can use them to access your account if you lose your phone.
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<div class="backup-codes">
|
|
489
|
+
<% @backup_codes.each do |code| %>
|
|
490
|
+
<code><%= code %></code>
|
|
491
|
+
<% end %>
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<%= link_to "Continue", root_path, class: "btn btn-primary" %>
|
|
495
|
+
</div>
|
|
496
|
+
ERB
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def credentials_template
|
|
500
|
+
<<~YAML
|
|
501
|
+
hokipoki:
|
|
502
|
+
otp_encryption_key: #{SecureRandom.hex(32)}
|
|
503
|
+
vector_encryption_key: #{SecureRandom.hex(32)}
|
|
504
|
+
github:
|
|
505
|
+
client_id: your_github_client_id_here
|
|
506
|
+
client_secret: your_github_client_secret_here
|
|
507
|
+
YAML
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def timestamp
|
|
511
|
+
Time.current.strftime("%Y%m%d%H%M%S")
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
end
|