shimmer 0.0.12 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/shimmer/auth/apple_provider.rb +30 -0
- data/lib/shimmer/auth/authenticating.rb +37 -0
- data/lib/shimmer/auth/current.rb +15 -0
- data/lib/shimmer/auth/dev_provider.rb +14 -0
- data/lib/shimmer/auth/device.rb +23 -0
- data/lib/shimmer/auth/google_provider.rb +16 -0
- data/lib/shimmer/auth/user.rb +22 -0
- data/lib/shimmer/auth.rb +47 -0
- data/lib/shimmer/helpers/meta_helper.rb +32 -0
- data/lib/shimmer/railtie.rb +8 -0
- data/lib/shimmer/tasks/auth.rake +19 -0
- data/lib/shimmer/tasks/db.rake +9 -0
- data/lib/shimmer/tasks/s3.rake +46 -0
- data/lib/shimmer/utils/config.rb +33 -0
- data/lib/shimmer/utils/meta.rb +30 -0
- data/lib/shimmer/version.rb +1 -1
- data/lib/shimmer.rb +1 -0
- metadata +15 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 789f3862287d4a4e4ea0ea64429d5855d147853d25a85f896acde0f4a456f805
|
4
|
+
data.tar.gz: 5fe592396ee46ae62468f4436835b1a3718fedfe60823287388bbe28cbb51f92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ca1e5c4a38b5b36da2b9cc20bae29c2a20df60550f8e9904db8de6cca92aea4c02f62cdb57d1c72cbfb0b4d660b2714ac3051e6c9eb8996914d954ba5ec14a4
|
7
|
+
data.tar.gz: 50d5ffc3cac315ad169da4da82c621338b565598919930a3f3f72bd32f0321d2c781ba0598020762aaa5492b96a3988d6377ded829e4c650e4ebb3fb48361d5b
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
class AppleProvider < Provider
|
6
|
+
self.token_column = :apple_id
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def request_details(params)
|
11
|
+
name = params[:user] ? JSON.parse(params[:user])["name"] : {}
|
12
|
+
headers = {
|
13
|
+
'Content-Type': "application/x-www-form-urlencoded"
|
14
|
+
}
|
15
|
+
form = {
|
16
|
+
grant_type: "authorization_code",
|
17
|
+
code: params[:code],
|
18
|
+
client_id: Config.instance.apple_bundle_id!,
|
19
|
+
client_secret: Config.instance.apple_client_secret,
|
20
|
+
scope: "name email"
|
21
|
+
}
|
22
|
+
response = HTTParty.post("https://appleid.apple.com/auth/token", body: URI.encode_www_form(form), headers: headers)
|
23
|
+
raise InvalidTokenError, "Login check failed: #{response.body}" unless response.ok?
|
24
|
+
|
25
|
+
token = JWT.decode(response["id_token"], nil, false).first
|
26
|
+
UserDetails.new token: token["sub"], email: token["email"], first_name: name["firstName"], last_name: name["lastName"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
module Authenticating
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_action :authenticate
|
10
|
+
helper_method :current_user
|
11
|
+
|
12
|
+
def require_login
|
13
|
+
redirect_to login_path unless current_user
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_user
|
17
|
+
::Current.user
|
18
|
+
end
|
19
|
+
|
20
|
+
def login(device:)
|
21
|
+
::Current.device = device
|
22
|
+
cookies.encrypted[:device_token] = {value: device.token, expires: 2.years.from_now}
|
23
|
+
end
|
24
|
+
|
25
|
+
def logout
|
26
|
+
cookies.delete :device_token
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def authenticate
|
32
|
+
::Current.device = cookies.encrypted[:device_token].presence&.then { |e| ::Device.find_by token: e }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
class DevProvider < Provider
|
6
|
+
def login(email:, user_agent: nil, ip: nil)
|
7
|
+
user = model.find_or_create_by!(email: email)
|
8
|
+
device = user.devices.create! user_agent: user_agent
|
9
|
+
log_login(user, device_id: device.id, user_agent: user_agent, ip: ip)
|
10
|
+
device
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
module Device
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
has_secure_token
|
10
|
+
|
11
|
+
def name
|
12
|
+
[browser.platform.name, browser.name].join(" ")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def browser
|
18
|
+
@browser ||= Browser.new user_agent
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
class GoogleProvider < Provider
|
6
|
+
self.token_column = :google_id
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def request_details(params)
|
11
|
+
payload = GoogleIDToken::Validator.new.check(params[:credential], Config.instance.google_client_id!)
|
12
|
+
UserDetails.new token: payload["sub"], email: payload["email"], first_name: payload["given_name"].presence, last_name: payload["family_name"].presence
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
module User
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
def authenticate!(user_agent: nil, ip: nil)
|
10
|
+
Provider.new(self.class).create_device(user: self, user_agent: user_agent, ip: ip)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def login!(provider:, **attributes)
|
16
|
+
"Shimmer::Auth::#{provider.to_s.classify}Provider".constantize
|
17
|
+
.new(self).login(**attributes)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/shimmer/auth.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module Auth
|
5
|
+
class Provider
|
6
|
+
class InvalidTokenError < StandardError; end
|
7
|
+
UserDetails = Struct.new(:token, :email, :first_name, :last_name, keyword_init: true)
|
8
|
+
attr_reader :model
|
9
|
+
cattr_accessor :token_column
|
10
|
+
|
11
|
+
def initialize(model)
|
12
|
+
@model = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def login(params:, user_agent: nil, ip: nil)
|
16
|
+
user = fetch_user request_details(params)
|
17
|
+
create_device user: user, user_agent: user_agent, ip: ip
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_device(user:, user_agent: nil, ip: nil)
|
21
|
+
user.devices.create!(user_agent: user_agent).tap do |device|
|
22
|
+
log_login(user, device_id: device.id, user_agent: user_agent, ip: ip)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def log_login(user, device_id:, user_agent: nil, ip: nil)
|
29
|
+
return unless user.respond_to? :publish
|
30
|
+
|
31
|
+
user.publish :login, provider: self.class.name.demodulize.underscore, device_id: device_id, user_agent: user_agent, ip: ip
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_user(details)
|
35
|
+
user = model.find_by(token_column => details.token) || model.find_by(email: details.email) || model.new
|
36
|
+
user[token_column] ||= details.token
|
37
|
+
user.email ||= details.email
|
38
|
+
user.first_name ||= details.first_name
|
39
|
+
user.last_name ||= details.last_name
|
40
|
+
user.save! if user.changed?
|
41
|
+
user
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Dir["#{File.expand_path("./auth", __dir__)}/*"].sort.each { |e| require e }
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
module MetaHelper
|
5
|
+
def meta
|
6
|
+
@meta ||= Meta.new.tap do |meta|
|
7
|
+
meta.canonical = url_for(only_path: false)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def title(value)
|
12
|
+
meta.title = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def description(value)
|
16
|
+
meta.description = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def image(value)
|
20
|
+
meta.image = image_file_url(value, width: 1200)
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_meta
|
24
|
+
tags = meta.tags.map do |tag|
|
25
|
+
type = tag.delete(:type) || "meta"
|
26
|
+
value = tag.delete(:value)
|
27
|
+
content_tag(type, value, tag)
|
28
|
+
end
|
29
|
+
safe_join tags, "\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/shimmer/railtie.rb
CHANGED
@@ -8,3 +8,11 @@ module Shimmer
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
ActiveSupport.on_load(:action_view) do
|
13
|
+
Dir.glob("#{File.expand_path(__dir__)}/helpers/**/*.rb").each do |file|
|
14
|
+
load file
|
15
|
+
name = file.split("/").last.delete_suffix(".rb").classify
|
16
|
+
include "Shimmer::#{name}".constantize
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :auth do
|
4
|
+
desc "Generates a Sign in with Apple Token"
|
5
|
+
task :apple_token do
|
6
|
+
ecdsa_key = OpenSSL::PKey::EC.new IO.read ".apple-key.p8"
|
7
|
+
headers = {
|
8
|
+
"kid" => Shimmer::Config.instance.apple_key_id!
|
9
|
+
}
|
10
|
+
claims = {
|
11
|
+
"iss" => Shimmer::Config.instance.apple_team_id!,
|
12
|
+
"iat" => Time.now.to_i,
|
13
|
+
"exp" => 180.days.from_now.to_i,
|
14
|
+
"aud" => "https://appleid.apple.com",
|
15
|
+
"sub" => Shimmer::Config.instance.apple_bundle_id!
|
16
|
+
}
|
17
|
+
puts JWT.encode claims, ecdsa_key, "ES256", headers
|
18
|
+
end
|
19
|
+
end
|
data/lib/shimmer/tasks/db.rake
CHANGED
@@ -43,4 +43,13 @@ namespace :db do
|
|
43
43
|
|
44
44
|
desc "Download all app data, including assets"
|
45
45
|
task pull: [:pull_data, :pull_assets]
|
46
|
+
|
47
|
+
desc "Migrates if the database has any tables."
|
48
|
+
task migrate_if_tables: :environment do
|
49
|
+
if ActiveRecord::Base.connection.tables.any?
|
50
|
+
Rake::Task["db:migrate"].invoke
|
51
|
+
else
|
52
|
+
puts "No tables in database yet, skipping migration"
|
53
|
+
end
|
54
|
+
end
|
46
55
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :s3 do
|
4
|
+
desc "Creates a new S3 bucket and outputs or uploads the credentials."
|
5
|
+
task :create_bucket do
|
6
|
+
puts "Please enter the name for your new bucket"
|
7
|
+
name = $stdin.gets.strip
|
8
|
+
region = "eu-central-1"
|
9
|
+
sh "aws s3 mb s3://#{name} --region #{region}"
|
10
|
+
sh "aws iam create-user --user-name #{name}"
|
11
|
+
policy = <<~JSON
|
12
|
+
{
|
13
|
+
"Version": "2012-10-17",
|
14
|
+
"Statement": [
|
15
|
+
{
|
16
|
+
"Effect": "Allow",
|
17
|
+
"Action": [
|
18
|
+
"s3:CreateBucket",
|
19
|
+
"s3:DeleteObject",
|
20
|
+
"s3:Put*",
|
21
|
+
"s3:Get*",
|
22
|
+
"s3:List*"
|
23
|
+
],
|
24
|
+
"Resource": [
|
25
|
+
"arn:aws:s3:::#{name}",
|
26
|
+
"arn:aws:s3:::#{name}/*"
|
27
|
+
]
|
28
|
+
}
|
29
|
+
]
|
30
|
+
}
|
31
|
+
JSON
|
32
|
+
File.write("policy.json", policy)
|
33
|
+
sh "aws iam put-user-policy --user-name #{name} --policy-name #{name} --policy-document file://policy.json"
|
34
|
+
File.delete("policy.json")
|
35
|
+
content = JSON.parse `aws iam create-access-key --user-name #{name}`
|
36
|
+
id = content.dig("AccessKey", "AccessKeyId")
|
37
|
+
secret = content.dig("AccessKey", "SecretAccessKey")
|
38
|
+
puts "Credentials and bucket were generated. Automatically assign them to the associated Heroku project? This will override and delete all current keys on Heroku. (y/n)"
|
39
|
+
vars = {AWS_REGION: region, AWS_BUCKET: name, AWS_ACCESS_KEY_ID: id, AWS_SECRET_ACCESS_KEY: secret}
|
40
|
+
if $stdin.gets.strip == "y"
|
41
|
+
sh "heroku config:set #{vars.map { |k, v| "#{k}=#{v}" }.join(" ")}"
|
42
|
+
else
|
43
|
+
vars.each { |k, v| puts "#{k}=#{v}" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
class Config
|
5
|
+
include Singleton
|
6
|
+
class MissingConfigError < StandardError; end
|
7
|
+
|
8
|
+
def method_missing(method_name)
|
9
|
+
method_name = method_name.to_s
|
10
|
+
type = :string
|
11
|
+
key = method_name.delete_suffix("!").delete_suffix("?")
|
12
|
+
required = method_name.end_with?("!")
|
13
|
+
type = :bool if method_name.end_with?("?")
|
14
|
+
value = ENV[key.upcase].presence
|
15
|
+
value ||= Rails.application.credentials.send(key)
|
16
|
+
raise MissingConfigError, "#{key.upcase} environment value is missing" if required && value.blank?
|
17
|
+
|
18
|
+
coerce value, type
|
19
|
+
end
|
20
|
+
|
21
|
+
def respond_to_missing?(method_name)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def coerce(value, type)
|
28
|
+
return !value.in?(["n", "0", "no", "false"]) if type == :bool && value.is_a?(String)
|
29
|
+
|
30
|
+
value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Shimmer
|
4
|
+
class Meta
|
5
|
+
class_attribute :app_name
|
6
|
+
attr_accessor :title, :description, :image, :canonical
|
7
|
+
|
8
|
+
def tags
|
9
|
+
tags = []
|
10
|
+
title = self.title.present? ? "#{self.title} | #{app_name}" : app_name
|
11
|
+
tags.push(type: :title, value: title)
|
12
|
+
tags.push(property: "og:title", content: title)
|
13
|
+
if description.present?
|
14
|
+
tags.push(name: :description, content: description)
|
15
|
+
tags.push(property: "og:description", content: description)
|
16
|
+
end
|
17
|
+
if image.present?
|
18
|
+
tags.push(property: "og:image", content: image)
|
19
|
+
end
|
20
|
+
if canonical.present?
|
21
|
+
tags.push(type: :link, rel: :canonical, href: canonical)
|
22
|
+
tags.push(property: "og:url", content: canonical)
|
23
|
+
end
|
24
|
+
tags.push(property: "og:type", content: :website)
|
25
|
+
tags.push(property: "og:locale", content: I18n.locale)
|
26
|
+
tags.push(name: "twitter:card", content: :summary_large_image)
|
27
|
+
tags
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/shimmer/version.rb
CHANGED
data/lib/shimmer.rb
CHANGED
@@ -6,6 +6,7 @@ Dir["#{File.expand_path("../lib/shimmer/middlewares", __dir__)}/*"].sort.each {
|
|
6
6
|
Dir["#{File.expand_path("../lib/shimmer/controllers", __dir__)}/*"].sort.each { |e| require e }
|
7
7
|
Dir["#{File.expand_path("../lib/shimmer/jobs", __dir__)}/*"].sort.each { |e| require e }
|
8
8
|
Dir["#{File.expand_path("../lib/shimmer/utils", __dir__)}/*"].sort.each { |e| require e }
|
9
|
+
require_relative "shimmer/auth"
|
9
10
|
|
10
11
|
module Shimmer
|
11
12
|
class Error < StandardError; end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shimmer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Ravens
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-06-
|
11
|
+
date: 2022-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -135,16 +135,29 @@ files:
|
|
135
135
|
- bin/solargraph
|
136
136
|
- config/rubocop_base.yml
|
137
137
|
- lib/shimmer.rb
|
138
|
+
- lib/shimmer/auth.rb
|
139
|
+
- lib/shimmer/auth/apple_provider.rb
|
140
|
+
- lib/shimmer/auth/authenticating.rb
|
141
|
+
- lib/shimmer/auth/current.rb
|
142
|
+
- lib/shimmer/auth/dev_provider.rb
|
143
|
+
- lib/shimmer/auth/device.rb
|
144
|
+
- lib/shimmer/auth/google_provider.rb
|
145
|
+
- lib/shimmer/auth/user.rb
|
138
146
|
- lib/shimmer/controllers/files_controller.rb
|
139
147
|
- lib/shimmer/controllers/sitemaps_controller.rb
|
148
|
+
- lib/shimmer/helpers/meta_helper.rb
|
140
149
|
- lib/shimmer/jobs/sitemap_job.rb
|
141
150
|
- lib/shimmer/middlewares/cloudflare.rb
|
142
151
|
- lib/shimmer/railtie.rb
|
152
|
+
- lib/shimmer/tasks/auth.rake
|
143
153
|
- lib/shimmer/tasks/db.rake
|
144
154
|
- lib/shimmer/tasks/lint.rake
|
155
|
+
- lib/shimmer/tasks/s3.rake
|
156
|
+
- lib/shimmer/utils/config.rb
|
145
157
|
- lib/shimmer/utils/file_helper.rb
|
146
158
|
- lib/shimmer/utils/file_proxy.rb
|
147
159
|
- lib/shimmer/utils/localizable.rb
|
160
|
+
- lib/shimmer/utils/meta.rb
|
148
161
|
- lib/shimmer/utils/remote_navigation.rb
|
149
162
|
- lib/shimmer/utils/sitemap_adapter.rb
|
150
163
|
- lib/shimmer/version.rb
|