{"id":22071474,"url":"https://github.com/zonque/altcha-rails","last_synced_at":"2026-05-14T17:02:05.036Z","repository":{"id":221986571,"uuid":"755952888","full_name":"zonque/altcha-rails","owner":"zonque","description":"Ruby gem to integrate ALTCHA to Ruby on Rails applications","archived":false,"fork":false,"pushed_at":"2024-04-28T17:19:06.000Z","size":13,"stargazers_count":6,"open_issues_count":2,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-11T07:53:15.622Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zonque.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-11T15:12:58.000Z","updated_at":"2026-01-08T02:09:53.000Z","dependencies_parsed_at":"2024-11-30T20:31:55.877Z","dependency_job_id":"7bfaab3a-cd18-4b05-b3dc-e0a149aa3abe","html_url":"https://github.com/zonque/altcha-rails","commit_stats":null,"previous_names":["zonque/altcha-rails"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/zonque/altcha-rails","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonque%2Faltcha-rails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonque%2Faltcha-rails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonque%2Faltcha-rails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonque%2Faltcha-rails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zonque","download_url":"https://codeload.github.com/zonque/altcha-rails/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zonque%2Faltcha-rails/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33034788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-30T20:31:44.586Z","updated_at":"2026-05-14T17:02:05.030Z","avatar_url":"https://github.com/zonque.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Gem Version](https://badge.fury.io/rb/altcha-rails.svg)](https://badge.fury.io/rb/altcha-rails)\n\n# Ruby gem for ALTCHA\n\n[ALTCHA](https://altcha.org/) is a protocol designed for safeguarding against spam and abuse by utilizing a proof-of-work mechanism. This protocol comprises both a client-facing widget and a server-side verification process.\n\n`altcha-rails` is a Ruby gem that provides a simple way to integrate ALTCHA into your Ruby on Rails application.\n\nThe gem provides two module methods: `Altcha.create_challenge`, which produces a fresh challenge for the form, and `Altcha.verify`, which validates the widget's submission and records it in `Rails.cache` for replay protection.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'altcha-rails'\n```\n\nThen execute `bundle install`.\n\n## Configuration\n\nCreate `config/initializers/altcha.rb` with the following configuration options:\n\n```ruby\nAltcha.setup do |config|\n  config.hmac_key         = ENV.fetch('ALTCHA_HMAC_KEY')\n  config.algorithm        = 'SHA-256'             # default\n  config.max_number       = 1_000_000             # difficulty: upper bound for the proof-of-work nonce. default 1_000_000\n  config.timeout          = 5.minutes             # default 300 seconds; accepts integers or ActiveSupport durations\n  config.cache_key_prefix = 'altcha:solution:'    # default; prepended to the Rails.cache key used for replay protection\nend\n```\n\n`hmac_key` has no default — it must be set explicitly. The other options have the defaults shown above.\n\n## Challenge expiration\n\nEach challenge embeds an `expires` parameter in its salt — a Unix timestamp set to `Time.now + Altcha.timeout`.\nWhen the client responds, the server rejects the submission if that timestamp has passed.\n\nThe salt is laid out in the canonical v1 ALTCHA format — `\u003crandom_hex\u003e?expires=\u003cunix_seconds\u003e\u0026` — including the\ntrailing `\u0026` delimiter that closes the parameter list before the proof-of-work nonce is appended for hashing. This\ndelimiter is required by the protocol fix for [CVE-2025-68113](https://altcha.org/security-advisory/) and is enforced\nby `Altcha.verify`.\n\nAs users might complete the captcha before filling out a complex form, the `timeout` should be set to a reasonable\nvalue.\n\n## Replay attacks\n\nTo also guard against replay attacks within the configured `timeout` period, the gem records each accepted\nsolution in `Rails.cache`, keyed by the solution's HMAC signature. Entries are written with `expires_in: Altcha.timeout`\nand `unless_exist: true`, so a replayed submission within the timeout window is rejected atomically, and entries\nexpire automatically once the timeout has passed. No periodic cleanup is required.\n\nMake sure `Rails.cache` is configured to use a backend that is shared across all server processes (e.g.\n`:redis_cache_store`, `:mem_cache_store`, `:solid_cache_store`, or `:file_store`). The default `:memory_store` is\nper-process and would let a replay slip through on a different worker; `:null_store` disables replay protection\nentirely.\n\n## Issuing a challenge\n\n`Altcha.create_challenge` returns an `Altcha::Challenge` whose `#to_json` produces exactly the payload the widget expects. The widget accepts this JSON directly via its `challenge` attribute, so no separate `/altcha` route is needed:\n\n```erb\n\u003caltcha-widget challenge='\u003c%= Altcha.create_challenge.to_json %\u003e'\u003e\u003c/altcha-widget\u003e\n```\n\nInclude the ALTCHA javascript widget script in your asset pipeline; see [the ALTCHA documentation](https://altcha.org/docs/website-integration) for the widget itself.\n\n## Verifying a submission\n\nWhen the form is submitted, the widget sends a base64-encoded JSON payload in a hidden input named `altcha`. In the controller that handles the submission, verify it with:\n\n```ruby\ndef create\n  @model = Model.create(model_params)\n\n  unless Altcha.verify(params.permit(:altcha)[:altcha])\n    flash.now[:alert] = 'ALTCHA verification failed.'\n    render :new, status: :unprocessable_entity\n    return\n  end\n\n  # ...\nend\n```\n\n`Altcha.verify` returns the `Altcha::Submission` if the response is valid and has not been seen before within the\ntimeout window, and `nil` otherwise.\n\n## Development\n\n```\nbundle install\nbundle exec rake test\n```\n\nTests use Minitest and a small `FakeCache` shim that mimics the bits of `Rails.cache` the gem touches, so the suite runs without booting Rails.\n\n## Changelog\n\n### 0.1.0\n\nThis release is a substantial rework. The public API collapses to two module methods, and everything else (model, migration, controller, route, generator) is gone.\n\n**Highlights:**\n\n- **Security fix — [CVE-2025-68113](https://altcha.org/security-advisory/) (challenge splicing / replay).** Salt now follows the canonical v1 ALTCHA format `\u003crandom_hex\u003e?expires=\u003cunix_seconds\u003e\u0026`, and `Altcha.verify` normalises the salt to its trailing-`\u0026` form before recomputing the proof-of-work hash. A spliced salt no longer round-trips to the same digest.\n- **No more `AltchaSolution` ActiveRecord model or `altcha_solutions` table.** Replay protection lives in `Rails.cache` (keyed by the submission's HMAC signature, TTL = `Altcha.timeout`, atomic via `unless_exist: true`).\n- **No more generated controller or route.** The widget now accepts the challenge JSON inline via its `challenge` attribute, so the host application no longer needs an endpoint to serve challenges.\n- **No more `rails generate altcha:install` generator.** Configuration is one `Altcha.setup` block — see [Configuration](#configuration) above.\n- **Configuration knob renamed**: `num_range` (Range) → `max_number` (Integer). `hmac_key` no longer has a placeholder default and must be set explicitly. A new `cache_key_prefix` option (default `\"altcha:solution:\"`) lets you namespace the replay-tracking keys.\n\n#### Upgrade guide from 0.0.x\n\n**1. Confirm your cache backend.** It must be shared across all server processes. In production: `:redis_cache_store`, `:mem_cache_store`, `:solid_cache_store`, `:file_store`, or another shared backend. The default `:memory_store` is per-process and is unsafe here; `:null_store` disables replay protection entirely. See the [Challenge expiration](#challenge-expiration) section.\n\n**2. Delete the generated model:**\n\n```\nrm app/models/altcha_solution.rb\n```\n\nIf you have specs covering it, remove those too.\n\n**3. Delete the generated controller:**\n\n```\nrm app/controllers/altcha_controller.rb\n```\n\nIf you have specs or request tests covering it, remove those too.\n\n**4. Remove the route.** In `config/routes.rb`, delete the line:\n\n```ruby\nget '/altcha', to: 'altcha#new'\n```\n\n**5. Update your view to inline the challenge JSON.** Replace:\n\n```erb\n\u003caltcha-widget challengeurl=\"\u003c%= altcha_url %\u003e\"\u003e\u003c/altcha-widget\u003e\n```\n\nwith:\n\n```erb\n\u003caltcha-widget challenge='\u003c%= Altcha.create_challenge.to_json %\u003e'\u003e\u003c/altcha-widget\u003e\n```\n\nThe `challenge` attribute is part of the modern ALTCHA widget; the `challengeurl` round-trip is no longer required.\n\n**6. Generate a migration to drop the `altcha_solutions` table:**\n\n```\n$ rails generate migration DropAltchaSolutions\n```\n\nFill in the generated file as follows (the `down` block is provided so the migration is reversible — adjust the column list if your installation customised it):\n\n```ruby\nclass DropAltchaSolutions \u003c ActiveRecord::Migration[7.1]\n  def up\n    drop_table :altcha_solutions\n  end\n\n  def down\n    create_table :altcha_solutions do |t|\n      t.string  :algorithm\n      t.string  :challenge\n      t.string  :salt\n      t.string  :signature\n      t.integer :number\n\n      t.timestamps\n    end\n\n    add_index :altcha_solutions,\n              [:algorithm, :challenge, :salt, :signature, :number],\n              unique: true,\n              name: 'index_altcha_solutions'\n  end\nend\n```\n\nThen run `rails db:migrate`.\n\n**7. Replace the verification call.** The public API moves from the generated model to the gem itself. The return value flips from boolean to `Altcha::Submission`-or-`nil`, but the truthy/falsy semantics are unchanged:\n\n```ruby\n# Before (0.0.x):\nunless AltchaSolution.verify_and_save(params.permit(:altcha)[:altcha])\n  flash.now[:alert] = 'ALTCHA verification failed.'\n  render :new, status: :unprocessable_entity\n  return\nend\n\n# After (0.1.0):\nunless Altcha.verify(params.permit(:altcha)[:altcha])\n  flash.now[:alert] = 'ALTCHA verification failed.'\n  render :new, status: :unprocessable_entity\n  return\nend\n```\n\n**8. Remove `AltchaSolution.cleanup` calls.** Cache entries now expire automatically via `expires_in: Altcha.timeout`, so any scheduled job, rake task, or cron entry that called `AltchaSolution.cleanup` can be deleted.\n\n**9. Update your initializer.** Rename `num_range` to `max_number` (and switch from a Range to an Integer) and remove any placeholder `hmac_key = 'change-me'`:\n\n```ruby\n# Before (0.0.x):\nAltcha.setup do |config|\n  config.algorithm = 'SHA-256'\n  config.num_range = (50_000..500_000)\n  config.timeout   = 5.minutes\n  config.hmac_key  = 'change-me'\nend\n\n# After (0.1.0):\nAltcha.setup do |config|\n  config.hmac_key   = ENV.fetch('ALTCHA_HMAC_KEY')\n  config.max_number = 500_000\n  config.timeout    = 5.minutes\nend\n```\n\n## Contributing\n\nBug reports and pull requests are welcome.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzonque%2Faltcha-rails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzonque%2Faltcha-rails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzonque%2Faltcha-rails/lists"}