shimmer 0.0.12 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|