Skip to content

Commit

Permalink
Add ability to unlock florida st door (#800)
Browse files Browse the repository at this point in the history
  • Loading branch information
Imperiopolis authored Nov 20, 2023
1 parent b9f57bf commit 75a3504
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 18 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ gem "uglifier"
gem "coffee-rails", ">= 4.2.2"
gem "bootstrap-sass"
gem "jquery-datatables-rails", ">= 3.4.0"
gem 'jwt'

# Avoid low-severity security issue: https://github.com/advisories/GHSA-vr8q-g5c7-m54m
gem "nokogiri", ">= 1.11.0.rc4"
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ DEPENDENCIES
jbuilder
jquery-datatables-rails (>= 3.4.0)
jquery-rails (>= 4.3.5)
jwt
kaminari (>= 1.2.1)
launchy
nokogiri (>= 1.11.0.rc4)
Expand Down
1 change: 1 addition & 0 deletions app/assets/config/manifest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//= link admin.css
//= link door.js
//= link dues.js
//= link membership_note.js
//
Expand Down
60 changes: 60 additions & 0 deletions app/assets/javascripts/door.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
$(document).ready(() => {
const checkDoorAccessibility = async () => {
try {
const response = await fetch(`${window.accessControlUri}/api/v1/status`);
if (!response.ok) {
throw 'door control not ready';
}
$("#unlock-door").removeClass('disabled');
} catch (e) {
$("#unlock-door").addClass('disabled');
}
};

// Poll if door control is available and update button state
checkDoorAccessibility();
setInterval(checkDoorAccessibility, 5000);

$("#unlock-door").click(async () => {
if ($("#unlock-door").hasClass('disabled')) {
alert('Door control not accessible. Are you on the space Wi-Fi?');
return;
}

try {
// Fetch a short-lived token to authenticate with door control
const tokenResponse = await fetch(`/members/users/${window.userId}/access_control_token`);
if (!tokenResponse.ok) {
throw 'failed to get door token';
}
const tokenJson = await tokenResponse.json();

// Unlock the door
const openResponse = await fetch(`${window.accessControlUri}/api/v1/unlock`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${tokenJson.token}`
},
body: JSON.stringify({
seconds: window.accessControlUnlockSeconds,
}),
});
const openJson = await openResponse.json();
if (!openResponse.ok) {
throw openJson.message;
}

$("#unlock-error").addClass('hidden')
$("#unlock-success").removeClass('hidden');

// Hide the success message when the door should be locked again
clearTimeout(window.successTimeoutId);
window.successTimeoutId = setTimeout(() => {
$("#unlock-success").addClass('hidden');
}, window.accessControlUnlockSeconds * 1000);
} catch (e) {
$("#unlock-error").removeClass('hidden');
$("#error-text").text(`Failed to unlock door: ${e}`);
}
});
});
15 changes: 15 additions & 0 deletions app/controllers/members/access_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'jwt'

class Members::AccessController < Members::MembersController
def token
return head :unprocessable_entity unless current_user.space_access?

payload = {
sub: current_user.email,
nbf: Time.now.to_i - 30,
exp: Time.now.to_i + 30
}

render json: { token: JWT.encode(payload, ENV['ACCESS_CONTROL_SIGNING_KEY'], 'HS256') }
end
end
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ def general_member?
member? || key_member? || voting_member?
end

def space_access?
key_member? || voting_member?
end

def gravatar_url(size = 200)
email = gravatar_email || self.email
hash = email ? Digest::MD5.hexdigest(email.downcase) : nil
Expand Down
10 changes: 5 additions & 5 deletions app/views/members/key_members/edit.html.haml
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
%h1 Become a key member!

%p
Key members receive a door code that enables you to access Double Union
whenever you want to (24/7), host events, and bring guests of any gender.
Key members can access Double Union whenever they want to (24/7),
host events, and bring guests of any gender.

%p
To become a key member, first complete a
#{ link_to "Key Membership Orientation for 77 Falmouth", "https://docs.google.com/presentation/d/1ygUBN_4SqZSjDi3Sx8MPviAXQz5KdI6_IOstW9qtWzQ/edit#slide=id.g111195ab4e6_0_0", target: "_blank" }.
#{ link_to "Key Membership Orientation for 650 Florida #M", "https://docs.google.com/presentation/d/1ygUBN_4SqZSjDi3Sx8MPviAXQz5KdI6_IOstW9qtWzQ/edit#slide=id.g111195ab4e6_0_0", target: "_blank" }.

%p
You can also learn more in the
#{ link_to "Members Handbook section on key membership", "https://docs.google.com/document/d/1yYXAj8rzQMiYt2xFdzm2xEOe0LCHohWNrMWzad-4mtQ/edit#heading=h.lsg2zdao236h", target: "_blank" }.

%p
When you submit this form, you will receive a randomly-assigned key code. You
When you submit this form, you will be able to unlock the space from the app. You
can email the Membership Coordinator at membership@doubleunion.org with any
questions.

= form_tag members_user_key_members_path(current_user.id), method: "patch" do
%p
= check_box_tag "agreements[attended_events]", 1, false, required: true
= label_tag "agreements[attended_events]", "I have completed a Key Member Orientation for 77 Falmouth."
= label_tag "agreements[attended_events]", "I have completed a Key Member Orientation for 650 Florida #M."

%p
= check_box_tag "agreements[kick_out]", 1, false, required: true
Expand Down
1 change: 0 additions & 1 deletion app/views/members/users/_bookmarks.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
%ul
%li #{ link_to "Members mailing list", "https://groups.google.com/a/doubleunion.org/forum/#!forum/members", target: "_blank" }
%li #{ link_to "Members folder in Google Drive", "https://drive.google.com/folderview?id=0B6a_aDP-2fOVV2FQLW5FVTZ2Mjg", target: "_blank" } &mdash; For easy access, #{ link_to "add a shortcut in your own Google Drive", "https://support.google.com/drive/answer/2375057?hl=en", target: "_blank" }.
%li #{ link_to "Key code orientation", "https://docs.google.com/presentation/d/1ygUBN_4SqZSjDi3Sx8MPviAXQz5KdI6_IOstW9qtWzQ/edit", target: "_blank" } &mdash; Describes how to get access to the 77 Falmouth space, and how to enter the space.
%li #{ link_to "Members calendar", "https://www.google.com/calendar/embed?src=br12b81lfe63rggddlg0k92mko@group.calendar.google.com&ctz=America/Los_Angeles", target: "_blank" } &mdash; This is a view-only version of the calendar. To add or edit events, go to #{ link_to "Google Calendar", "https://calendar.google.com/calendar/", target: "_blank" } (it should show up among your calendars).
%li #{ link_to "Members Slack chat", "https://doubleunion.slack.com/", target: "_blank" }
%li #{ link_to "DU web application code on GitHub", "https://github.com/doubleunion", target: "_blank" }
41 changes: 31 additions & 10 deletions app/views/members/users/index.html.haml
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
:javascript
window.userId = #{current_user.id};
window.accessControlUri = '#{ENV["ACCESS_CONTROL_URI"]}';
window.accessControlUnlockSeconds = #{ENV["ACCESS_CONTROL_UNLOCK_SECONDS"]};

= content_for :js do
= javascript_include_tag :door

- if current_user.member? || current_user.key_member?
.mt-20
- unless current_user.voting_policy_agreement
= link_to "Become a voting member", edit_members_user_voting_members_path(current_user), class: "btn btn-default"

= render 'bookmarks'

%h3 Space Access
- if current_user.member?
= link_to "Become a key member", edit_members_user_key_members_path(current_user), class: "btn btn-default"
- if current_user.door_code.present?
%p Your door code is #{current_user.door_code.code}*
- elsif current_user.key_member?
- if current_user.space_access?
%p Click the button below to unlock the door and access the space.
%p The door will automatically re-lock after #{ENV["ACCESS_CONTROL_UNLOCK_SECONDS"]} seconds.
#unlock-door.btn.btn-default.disabled Unlock Door
#unlock-success.hidden.bold
%p The door was unlocked!
#unlock-error.hidden
%p#error-text.text-danger.bold
%p
You are a key member, but you don't seem to have a door code set. If you need one, contact
=mail_to MEMBERSHIP_EMAIL
for help.
%i Note:
You must be at the space and connected to the Wi-Fi to unlock the door.
%table
%tr
%td.bold.text-right
ssid:&nbsp;
%td= ENV["WIFI_NETWORK_NAME"]
%tr
%td.bold.text-right
password:&nbsp;
%td= ENV["WIFI_NETWORK_PASSWORD"]
- elsif current_user.member?
= link_to "Become a key member", edit_members_user_key_members_path(current_user), class: "btn btn-default"

= render 'bookmarks'

- if @all_admins.any?
%h3 Admins
Expand Down
9 changes: 9 additions & 0 deletions config/application.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
#
SECRET_TOKEN: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

# If you want to test the access control system, you'll need to populate the
# signing key and local URI here.
ACCESS_CONTROL_SIGNING_KEY:
ACCESS_CONTROL_URI:
ACCESS_CONTROL_UNLOCK_SECONDS: '10'

WIFI_NETWORK_NAME: wifinetwork
WIFI_NETWORK_PASSWORD: wifipassword

# If you want to try sending email locally, you'll need make an AWS account and populate these values
# It's easier to just use Mailcatcher and Mailer previews, though!
AWS_ACCESS_KEY_ID:
Expand Down
2 changes: 1 addition & 1 deletion config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
config.serve_static_files = ENV["RAILS_SERVE_STATIC_FILES"].present?

# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
config.assets.js_compressor = Uglifier.new(harmony: true)
# config.assets.css_compressor = :sass

# Do not fallback to assets pipeline if a precompiled asset is missed.
Expand Down
2 changes: 1 addition & 1 deletion config/environments/staging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
config.serve_static_files = true

# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
config.assets.js_compressor = Uglifier.new(harmony: true)
# config.assets.css_compressor = :sass

# Do not fallback to assets pipeline if a precompiled asset is missed.
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
delete "cancel" => "dues#cancel"
post "scholarship_request" => "dues#scholarship_request"

get "access_control_token" => "access#token"

resource :key_members, only: [:edit, :update]
resource :voting_members, only: [:edit, :update]
end
Expand Down

0 comments on commit 75a3504

Please sign in to comment.