# Exercises Prism::Middleware::Session/Prism::CookieJar's `previous_secrets:` # rotation as a real, multi-process lifecycle against examples/blog - the # session-cookie counterpart to blog_rotation_demo.rb (which exercises the # same rotation contract for Prism::Orm::Encryption's `previous_keys:` # instead). See blog_session_secret_rotation_worker.rb for what each phase # actually proves over real HTTP. require 'fileutils' require 'rbconfig' ROOT = __dir__ DB_DIR = File.join(ROOT, 'examples/blog/db/session_rotation_demo') RUBY = RbConfig.ruby # Fresh scratch database every run, isolated from examples/blog/db/ # development.pstore (blog_demo.rb's own data) - re-running this file is # always deterministic and never fights blog_demo.rb for the same rows. FileUtils.rm_rf(DB_DIR) FileUtils.mkdir_p(DB_DIR) V1_SECRET = 'blog-demo-session-secret-v1'.freeze V2_SECRET = 'blog-demo-session-secret-v2'.freeze def run_phase(phase, env) puts '-' * 60 puts "phase: #{phase}" base_env = { 'PRISM_BLOG_DB_DIR' => DB_DIR, 'PRISM_BLOG_SESSION_PREVIOUS_SECRETS' => '' } pid = Process.spawn(base_env.merge(env), RUBY, '-Ilib', 'blog_session_secret_rotation_worker.rb', phase, chdir: ROOT) _, status = Process.wait2(pid) raise "phase #{phase} failed with #{status}" unless status.success? end run_phase('signup', 'PRISM_BLOG_SESSION_SECRET' => V1_SECRET) run_phase('rotate', 'PRISM_BLOG_SESSION_SECRET' => V2_SECRET, 'PRISM_BLOG_SESSION_PREVIOUS_SECRETS' => V1_SECRET) run_phase('post_rotation_final', 'PRISM_BLOG_SESSION_SECRET' => V2_SECRET) puts '-' * 60 puts 'blog_session_secret_rotation_demo done: a session written to during the rotation window (user A, via a ' \ 'real post creation) was transparently re-signed onto the new secret and survived full cutover; a session ' \ 'never written to again after signing in under the retired secret (user B) was correctly logged out, not ' \ 'crashed, once that secret was fully retired - all over real HTTP across independent processes.'