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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '038bb182d1dad60da8ef884f677bf291baed9ffb2e4a14959cd20e55c43f7ff0'
4
- data.tar.gz: d5f6c1489a4b285202c2d93d3c9bfd7e8a5ac4b3a17688452e3315782b694511
3
+ metadata.gz: 789f3862287d4a4e4ea0ea64429d5855d147853d25a85f896acde0f4a456f805
4
+ data.tar.gz: 5fe592396ee46ae62468f4436835b1a3718fedfe60823287388bbe28cbb51f92
5
5
  SHA512:
6
- metadata.gz: f10bade660c8ef17b62388e7ea9ac5d9b7bf40c658e744a1b9050a70bbabb7e2a360d1d10fdceb6fbffa43a002d94113efb3a794d8c233ad75387840f62d60bd
7
- data.tar.gz: fbdba908476796302ef9db666285fe57e39a69ae095374d11291cf2ea43c7bfe3bc1c5a6d74c23f2d46e815806ff6f95fe3b1c594834196d7169fe171b605f8a
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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shimmer
4
+ module Auth
5
+ module Current
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attribute :device
10
+
11
+ delegate :user, to: :device, allow_nil: true
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shimmer
4
- VERSION = "0.0.12"
4
+ VERSION = "0.0.15"
5
5
  end
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.12
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-08 00:00:00.000000000 Z
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