Skip to content

Commit 23dd863

Browse files
authored
Merge pull request #3 from ruby-no-kai/attendee-gate
attendee-gate
2 parents d1e9b4c + a013b44 commit 23dd863

File tree

11 files changed

+547
-0
lines changed

11 files changed

+547
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(import './docker-build-simple.libsonnet')('attendee-gate', 'us-west-2')

.github/workflows/docker-attendee-gate.yml

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

attendee-gate/.ruby-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.3

attendee-gate/Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM public.ecr.aws/lambda/ruby:3.3 as builder
2+
RUN dnf install -y gcc gcc-c++ make pkg-config git
3+
4+
COPY Gemfile* ${LAMBDA_TASK_ROOT}/
5+
WORKDIR ${LAMBDA_TASK_ROOT}/app
6+
7+
ENV BUNDLE_GEMFILE ${LAMBDA_TASK_ROOT}/Gemfile
8+
ENV BUNDLE_PATH ${LAMBDA_TASK_ROOT}/vendor/bundle
9+
ENV BUNDLE_DEPLOYMENT 1
10+
ENV BUNDLE_JOBS 16
11+
ENV BUNDLE_WITHOUT development:test
12+
RUN bundle install
13+
14+
COPY . ${LAMBDA_TASK_ROOT}/app
15+
16+
FROM public.ecr.aws/lambda/ruby:3.3
17+
18+
COPY --from=builder ${LAMBDA_TASK_ROOT}/vendor ${LAMBDA_TASK_ROOT}/vendor
19+
COPY . ${LAMBDA_TASK_ROOT}/app
20+
21+
WORKDIR ${LAMBDA_TASK_ROOT}/app
22+
23+
ENV BUNDLE_GEMFILE ${LAMBDA_TASK_ROOT}/Gemfile
24+
ENV BUNDLE_PATH ${LAMBDA_TASK_ROOT}/vendor/bundle
25+
ENV BUNDLE_DEPLOYMENT 1
26+
ENV BUNDLE_WITHOUT development:test
27+
CMD [ "index.AttendeeGate::Handlers.http" ]

attendee-gate/Gemfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
source 'https://rubygems.org'
2+
3+
gem 'sqlite3'
4+
gem 'aws-sdk-s3'
5+
gem 'aws-sdk-ssm'
6+
gem 'sinatra'
7+
gem 'apigatewayv2_rack', '>= 0.2.2'
8+
gem 'httpx'
9+
gem 'rexml'
10+
gem 'base64'
11+
12+
group :development do
13+
gem 'puma'
14+
end

attendee-gate/Gemfile.lock

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
GEM
2+
remote: https://rubygems.org/
3+
specs:
4+
apigatewayv2_rack (0.2.2)
5+
base64
6+
rack
7+
stringio
8+
aws-eventstream (1.3.1)
9+
aws-partitions (1.1061.0)
10+
aws-sdk-core (3.220.0)
11+
aws-eventstream (~> 1, >= 1.3.0)
12+
aws-partitions (~> 1, >= 1.992.0)
13+
aws-sigv4 (~> 1.9)
14+
base64
15+
jmespath (~> 1, >= 1.6.1)
16+
aws-sdk-kms (1.99.0)
17+
aws-sdk-core (~> 3, >= 3.216.0)
18+
aws-sigv4 (~> 1.5)
19+
aws-sdk-s3 (1.182.0)
20+
aws-sdk-core (~> 3, >= 3.216.0)
21+
aws-sdk-kms (~> 1)
22+
aws-sigv4 (~> 1.5)
23+
aws-sdk-ssm (1.191.0)
24+
aws-sdk-core (~> 3, >= 3.216.0)
25+
aws-sigv4 (~> 1.5)
26+
aws-sigv4 (1.11.0)
27+
aws-eventstream (~> 1, >= 1.0.2)
28+
base64 (0.2.0)
29+
http-2 (1.0.2)
30+
httpx (1.4.2)
31+
http-2 (>= 1.0.0)
32+
jmespath (1.6.2)
33+
logger (1.6.6)
34+
mini_portile2 (2.8.8)
35+
mustermann (3.0.3)
36+
ruby2_keywords (~> 0.0.1)
37+
nio4r (2.7.4)
38+
puma (6.6.0)
39+
nio4r (~> 2.0)
40+
rack (3.1.11)
41+
rack-protection (4.1.1)
42+
base64 (>= 0.1.0)
43+
logger (>= 1.6.0)
44+
rack (>= 3.0.0, < 4)
45+
rack-session (2.1.0)
46+
base64 (>= 0.1.0)
47+
rack (>= 3.0.0)
48+
rexml (3.4.1)
49+
ruby2_keywords (0.0.5)
50+
sinatra (4.1.1)
51+
logger (>= 1.6.0)
52+
mustermann (~> 3.0)
53+
rack (>= 3.0.0, < 4)
54+
rack-protection (= 4.1.1)
55+
rack-session (>= 2.0.0, < 3)
56+
tilt (~> 2.0)
57+
sqlite3 (2.6.0)
58+
mini_portile2 (~> 2.8.0)
59+
stringio (3.1.5)
60+
tilt (2.6.0)
61+
62+
PLATFORMS
63+
ruby
64+
65+
DEPENDENCIES
66+
apigatewayv2_rack (>= 0.2.2)
67+
aws-sdk-s3
68+
aws-sdk-ssm
69+
base64
70+
httpx
71+
puma
72+
rexml
73+
sinatra
74+
sqlite3
75+
76+
BUNDLED WITH
77+
2.6.3

attendee-gate/app.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
require_relative './db'
2+
require 'base64'
3+
require 'aws-sdk-s3'
4+
require 'sinatra/base'
5+
6+
module AttendeeGate
7+
class App < Sinatra::Base
8+
CONTEXT_NAME = 'attendeegate.ctx'
9+
DB_NAME = 'attendeegate.db'
10+
11+
class DataCache
12+
STALE_AFTER = 15
13+
Head = Data.define(:last_modified, :etag, :ts) do
14+
def self.from_s3_response(r)
15+
new(
16+
last_modified: r.last_modified,
17+
etag: r.etag,
18+
ts: Time.now,
19+
)
20+
end
21+
end
22+
23+
def initialize(context)
24+
@context = context
25+
@db = nil
26+
@file = nil
27+
@head = nil
28+
end
29+
30+
attr_reader :context
31+
32+
def value
33+
return @db unless stale?
34+
@db&.close
35+
@file&.unlink
36+
@db = nil
37+
@file, @db, @head = fetch_db
38+
@db
39+
end
40+
41+
def stale?
42+
return true unless @db && @head
43+
return false if (Time.now - @head.ts) < STALE_AFTER
44+
Head.from_s3_response(s3.head_object(bucket: context.s3_bucket, key: context.s3_key)) != @head
45+
end
46+
47+
private def fetch_db
48+
file = Tempfile.new
49+
r = s3.get_object(bucket: context.s3_bucket, key: context.s3_key, response_target: file)
50+
db = DB.open(path: file.path, hash_key: context.hash_key)
51+
@file, @db, @head = file, db, Head.from_s3_response(r)
52+
end
53+
54+
# @return [Aws::S3::Client]
55+
private def s3
56+
context.s3
57+
end
58+
end
59+
60+
Context = Data.define(:s3, :s3_bucket, :s3_key, :hash_key) do
61+
def inspect
62+
"#<#{self.class.name} #{self.__id__}>"
63+
end
64+
end
65+
66+
def self.rack(...)
67+
ctx = Context.new(...)
68+
db = DataCache.new(ctx)
69+
lambda do |env|
70+
env[CONTEXT_NAME] = ctx
71+
env[DB_NAME] = db
72+
self.call(env)
73+
end
74+
end
75+
76+
helpers do
77+
def context
78+
env.fetch(CONTEXT_NAME)
79+
end
80+
81+
def db
82+
env.fetch(DB_NAME).value
83+
end
84+
end
85+
86+
post '/validate' do
87+
given_email_hashed = params[:email_hashed]&.to_s&.then { Base64.urlsafe_decode64(_1) } rescue nil
88+
given_code = params[:code]&.to_s&.then { _1.include?('-') ? _1 : "#{_1}-1" }
89+
halt(400, "email_hashed is required") if !given_email_hashed || given_email_hashed.empty?
90+
halt(400, "code is required") if !given_code || given_code.empty?
91+
92+
result = nil
93+
94+
cand = db.find_attendee_by_code(given_code)
95+
if cand
96+
result = OpenSSL.fixed_length_secure_compare(cand.email_hashed, given_email_hashed) ? "ok" : "email_mismatch"
97+
end
98+
99+
unless result
100+
cands = db.find_attendees_by_email(given_email_hashed)
101+
result = cands.empty? ? "not_found" : "code_mismatch"
102+
end
103+
104+
content_type :json
105+
JSON.pretty_generate(
106+
meta: {
107+
db: db.current.to_h,
108+
},
109+
result:,
110+
)
111+
end
112+
end
113+
end

attendee-gate/config.ru

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
require_relative './app'
2+
require_relative './index'
3+
require 'logger'
4+
logger = Logger.new($stdout)
5+
$stdout.sync = true
6+
run AttendeeGate::App.rack(
7+
s3: Aws::S3::Client.new(logger:),
8+
s3_bucket: ENV.fetch('S3_BUCKET','rk-attendee-gate'),
9+
s3_key: ENV.fetch('S3_KEY','dev/dev.sqlite3'),
10+
hash_key: '',
11+
)

0 commit comments

Comments
 (0)