{"id":13396388,"url":"https://github.com/cyx/shield","last_synced_at":"2025-04-05T12:03:38.739Z","repository":{"id":56895312,"uuid":"985081","full_name":"cyx/shield","owner":"cyx","description":"Authentication protocol for use in your routing and model context","archived":false,"fork":false,"pushed_at":"2023-04-27T10:17:13.000Z","size":101,"stargazers_count":127,"open_issues_count":4,"forks_count":14,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-22T15:49:59.218Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/cyx.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":"2010-10-13T20:27:20.000Z","updated_at":"2025-01-29T12:13:26.000Z","dependencies_parsed_at":"2024-06-18T20:14:30.158Z","dependency_job_id":null,"html_url":"https://github.com/cyx/shield","commit_stats":{"total_commits":96,"total_committers":10,"mean_commits":9.6,"dds":"0.45833333333333337","last_synced_commit":"40e573a23f07aeb6d88adc2c36967ca67c5aa018"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyx%2Fshield","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyx%2Fshield/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyx%2Fshield/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyx%2Fshield/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyx","download_url":"https://codeload.github.com/cyx/shield/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332560,"owners_count":20921853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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-07-30T18:00:49.137Z","updated_at":"2025-04-05T12:03:38.718Z","avatar_url":"https://github.com/cyx.png","language":"Ruby","readme":"Shield\n======\n\nShield\n\n_n. A solid piece of metal code used to protect your application._\n\nWhy another authentication library?\n-----------------------------------\n\n1. Because most of the other libraries are too huge.\n2. Extending other libraries is a pain.\n3. Writing code is fun :-).\n\nWhat shield is\n--------------\n\n1. Simple (~ 110 lines of Ruby code).\n2. Doesn't get in the way.\n3. Treats you like a grown up.\n\nWhat shield is not\n------------------\n\n- is _not_ a ready-made end-to-end authentication solution.\n- is _not_ biased towards any kind of ORM.\n\nUnderstanding Shield in 15 minutes\n----------------------------------\n\n### Shield::Model\n\n`Shield::Model` is a very basic protocol for doing authentication\nagainst your model. It doesn't assume a lot, apart from the following:\n\n1. You will implement `User.fetch` which receives the login string.\n2. You have an attribute `crypted_password` which is able to store\n   up to __192__ characters.\n\nAnd that's it.\n\nIn order to implement the model protocol, you start by\nincluding `Shield::Model`.\n\n```ruby\nclass User \u003c Struct.new(:email, :crypted_password)\n  include Shield::Model\n\n  def self.fetch(email)\n    user = new(email)\n    user.password = \"pass1234\"\n\n    return user\n  end\nend\n```\n\nBy including `Shield::Model`, you get all the general methods needed\nin order to do authentication.\n\n1. You get `User.authenticate` which receives the login string and\n   password as the two parameters.\n2. You get `User#password=` which automatically converts the clear text\n   password into a hashed form and assigns it into `#crypted_password`.\n\n```ruby\nu = User.new(\"foo@bar.com\")\n\n# A password accessor has been added which manages `crypted_password`.\nu.password = \"pass1234\"\n\nShield::Password.check(\"pass1234\", u.crypted_password)\n# =\u003e true\n\n# Since we've hard coded all passwords to pass1234\n# we're able to authenticate properly.\nnil == User.authenticate(\"foo@bar.com\", \"pass1234\")\n# =\u003e false\n\n# If we try a different password on the other hand,\n# we get `nil`.\nnil == User.authenticate(\"foo@bar.com\", \"wrong\")\n# =\u003e true\n```\n\nShield uses [Armor][armor] for encrypting passwords. Armor is a pure ruby\nimplementation of [PBKDF2][pbkdf2], a password-based key derivation function\nrecommended for the protection of electronically-stored data.\n\nTo make Shield work with any ORM, make sure that an `.[]` method which\nfetches the user instance by id is implemented.\n\n[armor]: https://github.com/cyx/armor\n[pbkdf2]: http://en.wikipedia.org/wiki/PBKDF2\n\n```ruby\nclass User\n  include Shield::Model\n\n  # ...\n\n  def self.[](id)\n    get id\n  end\nend\n```\n\n### Logging in with an email and username?\n\nIf your requirements dictate that you need to be able to support logging\nin using either username or email, then you can simply extend `User.fetch`\na bit by doing:\n\n```ruby\n# in Sequel (http://sequel.rubyforge.org)\nclass User \u003c Sequel::Model\n  include Shield::Model\n\n  def self.fetch(identifier)\n    filter(email: identifier).first || filter(username: identifier).first\n  end\nend\n\n# in Ohm (http://ohm.keyvalue.org)\nclass User \u003c Ohm::Model\n  include Shield::Model\n\n  attribute :email\n  attribute :username\n\n  unique :email\n  unique :username\n\n  def self.fetch(identifier)\n    with(:email, identifier) || with(:username, identifier)\n  end\nend\n```\n\nIf you want to allow case-insensitive logins for some reason, you can\nsimply normalize the values to their lowercase form.\n\n### Shield::Helpers\n\nAs the name suggests, `Shield::Helpers` is out there to aid you a bit,\nbut this time it aids you in the context of your Rack application.\n\n`Shield::Helpers` assumes only the following:\n\n1. You have included in your application a Session handler,\n   (e.g. Rack::Session::Cookie)\n2. You have an `env` method which returns the environment hash as\n   was passed in Rack.\n\n**Note:** As of this writing, Sinatra, Cuba \u0026 Rails adhere to having an `env`\nmethod in the handler / controller context. Shield also ships with tests for\nboth Cuba and Sinatra.\n\n```ruby\nrequire \"sinatra\"\n\n# Satisfies assumption number 1 above.\nuse Rack::Session::Cookie\n\n# Mixes `Shield::Helpers` into your routes context.\nhelpers Shield::Helpers\n\nget \"/private\" do\n  error(401) unless authenticated(User)\n\n  \"Private\"\nend\n\nget \"/login\" do\n  erb :login\nend\n\npost \"/login\" do\n  if login(User, params[:login], params[:password])\n    remember(authenticated(User)) if params[:remember_me]\n    redirect(params[:return] || \"/\")\n  else\n    redirect \"/login\"\n  end\nend\n\nget \"/logout\" do\n  logout(User)\n  redirect \"/\"\nend\n\n__END__\n\n@@ login\n\u003ch1\u003eLogin\u003c/h1\u003e\n\n\u003cform action='/login' method='post'\u003e\n\u003cinput type='text' name='login' placeholder='Email'\u003e\n\u003cinput type='password' name='password' placeholder='Password'\u003e\n\u003cinput type='submit' name='proceed' value='Login'\u003e\n```\n\n**Note for the reader**: The redirect to `params[:return]` in the example\nis vulnerable to URL hijacking. You can whitelist redirectable urls, or\nsimply make sure the URL matches the pattern `/\\A[\\/a-z0-9\\-]+\\z/i`.\n\n### Shield::Middleware\n\nIf you have a keen eye you might have noticed that instead of redirecting\naway to the login URL in the example above, we instead chose to do a\n`401 Unauthorized`. In strict HTTP Status code terms, this is the proper\napproach. The redirection is simply the user experience pattern that has\nemerged in web applications.\n\nBut don't despair! If you want to do redirects simply add\n`Shield::Middleware` to your middleware stack like so:\n\n```ruby\n# taken from example above\nuse Shield::Middleware, \"/login\"\nuse Rack::Session::Cookie\n\n# rest of code follows here\n# ...\n```\n\nNow when your application responds with a `401`, `Shield::Middleware`\nwill be responsible for doing the redirect to `/login`.\n\nIf you try and do a `curl --head http://localhost:4567/private` with\n`Shield::Middleware`, you'll get a response similar to the following:\n\n```\nHTTP/1.1 302 Found\nLocation: http://localhost:4567/login?return=%2Fprivate\nContent-Type: text/html\n```\n\nNotice that it specifies `/private` as the return URL.\n\n## Installation\n\nYou can install it using rubygems:\n\n```\ngem install shield\n```\n","funding_links":[],"categories":["Cuba Components \u0026 Related Gems","Authentication and OAuth","Middlewares"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyx%2Fshield","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyx%2Fshield","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyx%2Fshield/lists"}