Repository navigation
Authentication System
Part of FreeREG-Core-Records (shared with FreeCEN).
Models: app/models/user.rb, app/models/userid_detail.rb · Controllers: app/controllers/users/sessions_controller.rb, app/controllers/users/passwords_controller.rb, app/controllers/manage_resources_controller.rb, app/controllers/application_controller.rb · Config: config/initializers/devise.rb, config/routes.rb (devise_for :users), lib/userid_role.rb
Authentication is Devise on a Mongoid User model. The member's profile, role and permissions live on a separate model, UseridDetail (UseridDetail-System), and the two are linked by User#userid_detail_id. Logging in authenticates a User. Everything after that (menus, roles, county/syndicate scope) works from UseridDetail, found via session and cookie values that ApplicationController keeps in step with Devise.
Much of the code still says "Refinery" (save_to_refinery, check_exists_in_refinery, current_authentication_devise_user, a refinery/authentication/devise view folder). These are leftovers from when login went through the Refinery CMS's Devise user. The Refinery gems are no longer loaded. Where the code says "Refinery user", read it as the Devise User described here.
User (Devise) |
UseridDetail (profile) |
|
|---|---|---|
| Identity |
username (= the member's userid), email
|
userid, email_address
|
| Password | encrypted_password |
password (the same hash, copied across) |
| Devise modules |
database_authenticatable, registerable, trackable, recoverable, rememberable, validatable, encryptable (encryptor :freereg) |
none |
| Link |
userid_detail_id (String) |
found by username ↔ userid, case-insensitively |
| Role | none |
person_role, secondary_role, county_groups, syndicate_groups, active
|
Keeping them in sync (app/models/userid_detail.rb):
-
after_create :save_to_refineryfinds or creates theUserwith the same username and copiesuserid,email_addressand the already-hashedpasswordinto it. It also sets a reset-password token, so new members can be sent a "set your password" link. -
before_destroy :delete_refinery_user_and_userid_folderdeletes theUser(and the member's datafiles folder). - Email changes are validated against both collections, so an address can't be taken by one member's
Userand another member'sUseridDetail. -
after_update :update_refineryis commented out. Changing a member's userid or email onUseridDetaildoesn't automatically update theUser, except through the explicit code paths inUseridDetailsController. If someone can't log in after an admin edit, compare the two records.
User#before_update :inform_coordinator_of_completion_and_update_userid: when a newly registered transcriber sets their own password for the first time, the syndicate coordinator is emailed that registration is complete (finish_transcriber_creation_setup).
Passwords are hashed by a custom Devise encryptor, Devise::Encryptable::Encryptors::Freereg (defined at the top of config/initializers/devise.rb), kept for compatibility with passwords migrated from the original FreeREG system.
Routes (config/routes.rb): GET/POST /users/sign_in, DELETE /users/sign_out, GET /users/password/new, GET /users/password/edit, PATCH /users/password, all to the custom controllers in app/controllers/users/.
-
Users::SessionsController#createpermitslogin,password,remember_meand callswarden.authenticate!.User.find_for_database_authenticationmatcheslogincase-insensitively against email, or username (a login containing@is only tried as an email). Any exception is logged and shown as "Invalid email or password". -
ApplicationController#after_sign_in_path_forrecords whichUseridDetailthe session belongs to and sends the member to/manage_resources/new. -
ManageResourcesController#new(is_ok_to_render_actions?) is the gatekeeper:- the
UseridDetailmust exist and beactive; - roles
researcherandpendingare refused ("functions still under development"); - if
member_openis false inmongo_config.yml(maintenance mode), onlysystem_administratorandtechnicalget in. The sign-in page shows a maintenance notice in the same case; - members whose email confirmation is due are sent to
/userid_details/confirm_email_address(need_to_confirm_email_address?); -
computeraccounts (scripted uploaders) get their own minimal page.
- the
-
set_sessionstoressession[:userid],session[:user_id],session[:first_name],session[:manager]andsession[:role], the role the member is acting in. The menu shown isUseridRole::OPTIONS[session[:role]](lib/userid_role.rb).
A member can hold one person_role plus any number of secondary_roles. Members with more than one role pick the role they're acting in from the dashboard's "Change Role" drop-down, and it's stored as session[:role]. Most permission checks compare session[:role] (e.g. reject_access … unless session[:role] == 'system_administrator'), so what a member can do depends on the role they're currently acting in.
ApplicationController runs before_action :require_login on every controller unless skipped:
- not signed in → clear
session[:userid_detail_id],session[:devise]and theuseridcookie, flash "You must be logged in", redirect to the search page; - signed in but
User#userid_detail_idblank → sign out with "account is not linked to a member profile"; - otherwise, keep the session values that older code relies on in step with the signed-in
User.
get_user returns the current member's UseridDetail. get_user_info_from_userid additionally loads @user, @manager, @roles (menu for session[:role]) and favourites. Most coordinator actions start with it.
Public pages (search, Database Contents, registration, contact and feedback forms) opt out per controller with skip_before_action :require_login. When adding a controller, it requires login unless you explicitly skip it. /open requires login.
-
Logout (
ManageResourcesController#logout) calls Devisesign_out(:user), needed to clear the remember-me cookie, then deletes theuseridand remember cookies andreset_session. -
Forgotten password:
Users::PasswordsControlleris standard Deviserecoverable(email with a token valid for 6 hours → set a new password → signed in → redirected to the sign-in page).UseridDetail#change_password(the "change password" action in a member's profile) also just triggers Devise's reset email for the matchingUser. If noUserexists, it only flashes a warning.
| Symptom | Check |
|---|---|
| "Invalid email or password" for a correct password | Does a User exist with that username/email? Does User.encrypted_password equal UseridDetail.password? |
| "Your account is not linked to a member profile" |
User.userid_detail_id blank or pointing at a deleted UseridDetail
|
| Logged in but bounced back with "not active" |
UseridDetail.active is false |
| "The system is presently undergoing maintenance" |
member_open is false in mongo_config.yml
|
| Member sees the wrong menu |
session[:role]: they may be acting in a secondary role; switch with "Change Role" |
| Admin changed a member's email/userid but login still uses the old one |
update_refinery is disabled; update the User record as well |
© 2025 FreeUKGen. Licensed under Apache License 2.0