yiffspace-auth 0.0.1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/LICENSE +20 -0
  4. data/README.md +41 -0
  5. data/Rakefile +10 -0
  6. data/app/controllers/yiff_space/auth/application_controller.rb +11 -0
  7. data/app/controllers/yiff_space/auth/root_controller.rb +49 -0
  8. data/app/controllers/yiff_space/auth/webhook_controller.rb +73 -0
  9. data/app/helpers/yiff_space/auth_helper.rb +6 -0
  10. data/app/views/yiff_space/auth/root/permissions.html.erb +14 -0
  11. data/config/routes.rb +12 -0
  12. data/lib/tasks/yiffspace_auth_tasks.rake +41 -0
  13. data/lib/yiffspace/auth/api_user.rb +43 -0
  14. data/lib/yiffspace/auth/auth_info/anonymous.rb +60 -0
  15. data/lib/yiffspace/auth/auth_info.rb +78 -0
  16. data/lib/yiffspace/auth/client.rb +56 -0
  17. data/lib/yiffspace/auth/discord_info.rb +65 -0
  18. data/lib/yiffspace/auth/engine.rb +49 -0
  19. data/lib/yiffspace/auth/helper.rb +174 -0
  20. data/lib/yiffspace/auth/permissions.rb +87 -0
  21. data/lib/yiffspace/auth/set_client_name.rb +15 -0
  22. data/lib/yiffspace/auth/user_info/anonymous.rb +72 -0
  23. data/lib/yiffspace/auth/user_info.rb +87 -0
  24. data/lib/yiffspace/auth/version.rb +7 -0
  25. data/lib/yiffspace/auth.rb +81 -0
  26. data/lib/yiffspace/core_ext/action_dispatch/set_auth_client/scoped.rb +13 -0
  27. data/lib/yiffspace/core_ext/action_dispatch/set_auth_client.rb +13 -0
  28. data/lib/yiffspace/core_ext/logto/named_session_storage.rb +7 -0
  29. data/lib/yiffspace/extensions/action_dispatch/set_auth_client/scoped.rb +15 -0
  30. data/lib/yiffspace/extensions/action_dispatch/set_auth_client.rb +13 -0
  31. data/lib/yiffspace/extensions/logto/named_session_storage.rb +22 -0
  32. data/lib/yiffspace/logto_management_client.rb +136 -0
  33. data/lib/yiffspace/serializers/anonymous_auth_info_serializer.rb +23 -0
  34. data/lib/yiffspace/serializers/anonymous_user_info_serializer.rb +23 -0
  35. data/lib/yiffspace/serializers/auth_info_serializer.rb +23 -0
  36. data/lib/yiffspace/serializers/discord_info_serializer.rb +23 -0
  37. data/lib/yiffspace/serializers/permissions_serializer.rb +23 -0
  38. data/lib/yiffspace/serializers/user_info_serializer.rb +23 -0
  39. data/lib/yiffspace/utils/user_deduper.rb +66 -0
  40. metadata +153 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eaf8f70d591dd62b1fe71e4c6b0269b89178dbbfc96aa00d2f220dc0c051b625
4
+ data.tar.gz: 60e9eb43743d443d4cced565c7891fd491344d5274317b72a2a599d9c5118cb0
5
+ SHA512:
6
+ metadata.gz: eca3a821613c9e57b8216ecc8a80608bd2068b31a2521d281183dbe1bd3dd2750111dfa20727912d6076ab08e1a53750b77d8ff0a111602792e791d3bd47fa96
7
+ data.tar.gz: 3df50557690c89ffc87e32a94c30b9f3e664702a168fb441cb5a0aa3897928114f169a3da43b8f09bd4dca599906ae42a823d9a66a246e6336a93652584f5a3c
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## 0.0.1
2
+
3
+ - Initial release, extracted from the `yiffspace` gem's `YiffSpace::Auth::*`
4
+ engine.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Donovan_DMC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # YiffSpace::Auth
2
+
3
+ Logto-based auth engine for https://yiff.space and related projects. Extracted
4
+ from the [yiffspace](https://github.com/YiffSpace/Gem) gem, which this gem
5
+ depends on.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "yiffspace-auth"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ## Development
22
+
23
+ This gem depends on [`yiffspace`](https://github.com/YiffSpace/Gem) via git
24
+ (see the `Gemfile`). To develop against a local checkout of that repo instead
25
+ of the pushed `master` branch, point Bundler at it with a local override:
26
+
27
+ ```bash
28
+ $ bundle config set local.yiffspace ../Gem
29
+ ```
30
+
31
+ This is stored in `.bundle/config` (gitignored) and doesn't touch the
32
+ `Gemfile`/`Gemfile.lock`, so it only affects your machine. Remove it with
33
+ `bundle config unset local.yiffspace` to go back to the pushed branch.
34
+
35
+ ## Contributing
36
+
37
+ Go away
38
+
39
+ ## License
40
+
41
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("bundler/setup")
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load("rails/tasks/engine.rake")
7
+
8
+ load("rails/tasks/statistics.rake")
9
+
10
+ require("bundler/gem_tasks")
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Auth
5
+ class ApplicationController < ::YiffSpace::ApplicationController
6
+ include(Helper)
7
+
8
+ helper(Helper)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Auth
5
+ class RootController < ApplicationController
6
+ include(Helper)
7
+
8
+ before_action(:sync_auth_if_dirty!)
9
+
10
+ def show
11
+ client.sign_in(redirect_uri: auth_client_config.redirect_uri, post_redirect_uri: params[:path] || "/")
12
+ end
13
+
14
+ def cb
15
+ client.handle_sign_in_callback(url: request.original_url)
16
+ user = client.fetch_user_info
17
+ self.user = UserInfo.new(id: user["identities"]["discord"]["userId"], user: user, discord: user["identities"]["discord"]["details"]["rawData"])
18
+ token = client.access_token_claims(resource: auth_client_config.resource)
19
+ self.auth = AuthInfo.new(id: user["identities"]["discord"]["userId"], token: token, permissions: token["scope"].split, roles: user["roles"], client_id: auth_client_config.client_id)
20
+ end
21
+
22
+ def permissions; end
23
+
24
+ def logout
25
+ client.sign_out(post_logout_redirect_uri: request.base_url)
26
+ full_reset!
27
+ end
28
+
29
+ def debug
30
+ return render("yiffspace/error", locals: { message: "Access Denied" }, status: :forbidden) unless YiffSpace::Auth.enable_debug_action?
31
+
32
+ render(json: {
33
+ env: request.env.select { |env| env.start_with?("yiffspace.") },
34
+ params: params,
35
+ session: session,
36
+ client: auth_client_config.as_json.merge(client_secret: "[REDACTED]"),
37
+ user: client.fetch_user_info,
38
+ token: client.access_token_claims(resource: auth_client_config.resource),
39
+ })
40
+ end
41
+
42
+ private
43
+
44
+ def client
45
+ @client ||= auth_client_config.logto(self)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("openssl")
4
+ require("active_support/security_utils")
5
+
6
+ module YiffSpace
7
+ module Auth
8
+ class WebhookController < ApplicationController
9
+ skip_before_action(:verify_authenticity_token)
10
+
11
+ HANDLED_EVENTS = %w[
12
+ User.Roles.Updated
13
+ User.SuspensionStatus.Updated
14
+ User.Deleted
15
+ Role.Scopes.Updated
16
+ ].freeze
17
+ DIRTY_FLAG_TTL = 24.hours
18
+
19
+ def create
20
+ unless verify_signature
21
+ render(plain: "Unauthorized", status: :unauthorized)
22
+ return
23
+ end
24
+
25
+ payload = JSON.parse(request.raw_post)
26
+ event = payload["event"]
27
+
28
+ handle_event(event, payload) if HANDLED_EVENTS.include?(event)
29
+
30
+ head(:ok)
31
+ rescue JSON::ParserError
32
+ render(plain: "Bad Request", status: :bad_request)
33
+ end
34
+
35
+ private
36
+
37
+ def verify_signature
38
+ secret = auth_client_config.logto_webhook_secret
39
+ return true if secret.blank?
40
+
41
+ received = request.headers["logto-signature-sha-256"].to_s
42
+ expected = OpenSSL::HMAC.hexdigest("SHA256", secret, request.raw_post)
43
+ ActiveSupport::SecurityUtils.secure_compare(received, expected)
44
+ end
45
+
46
+ def handle_event(event, payload)
47
+ data = payload["data"] || {}
48
+
49
+ if event == "Role.Scopes.Updated"
50
+ handle_role_scopes_updated(data)
51
+ else
52
+ user_id = data["id"]
53
+ mark_dirty(user_id) if user_id.present?
54
+ end
55
+ end
56
+
57
+ def handle_role_scopes_updated(data)
58
+ role_id = data["id"]
59
+ return if role_id.blank?
60
+
61
+ management = auth_client_config.logto_management
62
+ users = management.get_users_with_role(role_id)
63
+ users.each { |u| mark_dirty(u["id"]) }
64
+ rescue StandardError => e
65
+ Rails.logger.error("[YiffSpace::Auth::WebhookController] Role.Scopes.Updated fan-out failed: #{e.message}")
66
+ end
67
+
68
+ def mark_dirty(user_id)
69
+ Rails.cache.write(format(Helper::DIRTY_FLAG_KEY, user_id), true, expires_in: DIRTY_FLAG_TTL)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module AuthHelper
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ <h1 class="title">Permissions</h1>
2
+ <% if logged_in? %>
3
+ <% if auth.permissions.any? %>
4
+ <ul class="permissions-list">
5
+ <% auth.permissions.each do |perm| %>
6
+ <li class="permission"><%= perm %></li>
7
+ <% end %>
8
+ </ul>
9
+ <% else %>
10
+ <p class="message">You have no permissions.</p>
11
+ <% end %>
12
+ <% else %>
13
+ <p class="message">You are not logged in.</p>
14
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ YiffSpace::Auth::Engine.routes.draw do
4
+ constraints(YiffSpace::Auth::SetClientName.default) do
5
+ post(:webhook, controller: :webhook)
6
+ get(:cb, controller: :root)
7
+ get(:logout, controller: :root)
8
+ get(:permissions, controller: :root)
9
+ get(:debug, controller: :root) if YiffSpace::Auth.enable_debug_action?
10
+ root(action: :show, controller: :root, as: :auth)
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace(:yiffspace) do
4
+ desc("Report and interactively remove duplicate/orphaned Logto users left behind by " \
5
+ "get_or_create_user races. Prompts for confirmation before deleting each one. " \
6
+ "Pass AUTH_CLIENT=name to use a registered auth client other than :default " \
7
+ "(e.g. AUTH_CLIENT=yiffyapi_manage).")
8
+ task(dedupe_users: :environment) do
9
+ client_name = (ENV["AUTH_CLIENT"] || YiffSpace::Auth::DEFAULT_CLIENT_NAME).to_sym
10
+ management = YiffSpace::Auth[client_name].logto_management
11
+
12
+ puts("Scanning users via auth client #{client_name.inspect}...")
13
+ result = YiffSpace::Utils::UserDeduper.scan(management)
14
+
15
+ puts("\n#{result[:conflicts].size} discord id(s) with more than one linked user (ambiguous - not handled by this task, review manually):")
16
+ result[:conflicts].each do |discord_id, group|
17
+ puts(" discordId=#{discord_id}: #{group.map { |u| "#{u.id} (#{u.name})" }.join(', ')}")
18
+ end
19
+
20
+ puts("\n#{result[:unresolved_orphans].size} orphaned user(s) with no matching linked account (not handled by this task, review manually):")
21
+ result[:unresolved_orphans].each do |user|
22
+ puts(" #{user.id} (name=#{user.name}, avatar=#{user.data.avatar})")
23
+ end
24
+
25
+ puts("\n#{result[:deletable_orphans].size} confirmed duplicate(s) - orphan left by a losing get_or_create_user race:")
26
+ if result[:deletable_orphans].empty?
27
+ puts(" none found")
28
+ else
29
+ result[:deletable_orphans].each do |orphan, keeper|
30
+ print(" delete #{orphan.id} (name=#{orphan.name}, avatar=#{orphan.data.avatar}), keeping #{keeper.id} (name=#{keeper.name}) for discordId=#{YiffSpace::Utils::UserDeduper.discord_id_for(keeper)}? [y/N] ")
31
+ answer = $stdin.gets&.strip&.downcase
32
+ if answer == "y"
33
+ management.delete_user(orphan.id)
34
+ puts(" deleted #{orphan.id}")
35
+ else
36
+ puts(" skipped")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Auth
5
+ class ApiUser
6
+ attr_reader(:data)
7
+
8
+ def initialize(data)
9
+ @data = Utils::OpenHash.from(data)
10
+ end
11
+
12
+ delegate(:id, :name, to: :data)
13
+
14
+ def discord_id
15
+ data.identities.discord.userId
16
+ end
17
+
18
+ def created_at
19
+ Time.zone.at(data.createdAt)
20
+ end
21
+
22
+ def updated_at
23
+ Time.zone.at(data.updatedAt)
24
+ end
25
+
26
+ def last_sign_in_at
27
+ data.lastSignInAt.nil? ? nil : Time.zone.at(data.lastSignInAt)
28
+ end
29
+
30
+ def discord
31
+ DiscordInfo.new(data.identities.discord.details.rawData)
32
+ end
33
+
34
+ def avatar
35
+ Images::Avatar.get_for(discord_id, :discord)
36
+ end
37
+
38
+ def banner
39
+ Images::Banner.get_for(discord_id, :discord)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("singleton")
4
+
5
+ module YiffSpace
6
+ module Auth
7
+ class AuthInfo
8
+ class Anonymous
9
+ include(::Singleton)
10
+
11
+ %i[id token].each do |attr|
12
+ define_method(attr) { |*, **| raise(NotImplementedError, "#{attr} is not present on anonymous auth") }
13
+ end
14
+
15
+ def roles
16
+ []
17
+ end
18
+
19
+ def permissions
20
+ Permissions.new([])
21
+ end
22
+
23
+ def has_permission?(*)
24
+ false
25
+ end
26
+
27
+ def anonymous?
28
+ true
29
+ end
30
+
31
+ # this feels wrong, but it hopefully shouldn't break anything
32
+ def present?
33
+ false
34
+ end
35
+
36
+ def blank?
37
+ true
38
+ end
39
+
40
+ def serializable_hash(*)
41
+ nil
42
+ end
43
+
44
+ def to_session
45
+ serializable_hash
46
+ end
47
+
48
+ def self.from_json(*)
49
+ Anonymous.instance
50
+ end
51
+
52
+ def self.from_session(data)
53
+ return nil if data.blank?
54
+
55
+ from_json(data)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Auth
5
+ class AuthInfo
6
+ attr_reader(:id, :token, :roles, :permissions, :client_id)
7
+
8
+ # @param id String
9
+ # @param roles Array(String)
10
+ # @param permissions Array(String)
11
+ # @param token LogtoClient::AccessTokenClaims
12
+ # @param client_id String
13
+ def initialize(id:, roles:, permissions:, token:, client_id:)
14
+ raise(ArgumentError, "no id provided") if id.blank?
15
+ raise(ArgumentError, "no token provided") if token.blank?
16
+ raise(ArgumentError, "no client id provided") if client_id.blank?
17
+
18
+ @id = id
19
+ @token = token
20
+ @roles = Array(roles)
21
+ @permissions = Permissions.new(permissions, separator: YiffSpace::Auth.get_by_id(client_id).permissions_separator)
22
+ @client_id = client_id
23
+ end
24
+
25
+ def anonymous?
26
+ false
27
+ end
28
+
29
+ # this feels wrong, but it hopefully shouldn't break anything
30
+ def present?
31
+ true
32
+ end
33
+
34
+ def blank?
35
+ false
36
+ end
37
+
38
+ def has_permission?(name)
39
+ permissions.include?(name.to_s)
40
+ end
41
+
42
+ def serializable_hash(*)
43
+ {
44
+ "id" => id,
45
+ "token" => token.as_json,
46
+ "roles" => roles,
47
+ "permissions" => permissions.values,
48
+ "client_id" => client_id,
49
+ }
50
+ end
51
+
52
+ def to_session
53
+ serializable_hash
54
+ end
55
+
56
+ def self.from_json(data)
57
+ raise(ArgumentError, "invalid data") if data.blank?
58
+
59
+ data = JSON.parse(data) if data.is_a?(String)
60
+ data = ::YiffSpace::Utils::OpenHash.from(data)
61
+
62
+ new(
63
+ id: data.id,
64
+ token: data.token,
65
+ roles: data.roles,
66
+ permissions: data.permissions,
67
+ client_id: data.client_id,
68
+ )
69
+ end
70
+
71
+ def self.from_session(data)
72
+ return nil if data.blank?
73
+
74
+ from_json(data)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative("../core_ext/logto/named_session_storage")
4
+ require("httparty")
5
+
6
+ module YiffSpace
7
+ module Auth
8
+ class Client
9
+ attr_accessor(:client_id, :client_secret, :scopes, :resource,
10
+ :redirect_uri, :server_url, :auth_session_key, :user_session_key,
11
+ :update_discord_images, :permissions_separator, :logto_webhook_secret)
12
+
13
+ attr_reader(:name)
14
+
15
+ def initialize(name)
16
+ @name = name.to_sym
17
+ @scopes = %i[openid offline_access profile roles identities custom_data]
18
+ @redirect_uri = "http://127.0.0.1:3000/auth/cb"
19
+ @server_url = "https://auth.yiff.space"
20
+ @auth_session_key = :"yiffspace_auth_#{name}"
21
+ @user_session_key = :"yiffspace_user_#{name}"
22
+ @update_discord_images = true
23
+ @permissions_separator = ":"
24
+ @logto_webhook_secret = nil
25
+ end
26
+
27
+ def logto(controller)
28
+ LogtoClient.new(
29
+ config: LogtoClient::Config.new(
30
+ endpoint: server_url,
31
+ app_id: client_id,
32
+ app_secret: client_secret,
33
+ scopes: scopes,
34
+ resources: [resource].compact,
35
+ ),
36
+ # Allow the client to redirect to other hosts (i.e. your Logto tenant)
37
+ navigate: ->(uri) { controller.redirect_to(uri, allow_other_host: true) },
38
+ # Controller has access to the session object
39
+ storage: LogtoClient::NamedSessionStorage.new("yiffspace", controller.session, app_id: @name),
40
+ )
41
+ end
42
+
43
+ def logto_management
44
+ @logto_management ||= LogtoManagementClient.new(self)
45
+ end
46
+
47
+ def fetch_discord_user(id)
48
+ response = HTTParty.get("https://discord.com/api/v10/users/#{id}", { headers: { "Authorization" => "Bot #{YiffSpace.config.discord_bot_token}" } })
49
+ return nil if response.code == 404
50
+ raise("failed to fetch discord user: #{response.code} #{response.message}\n#{response.parsed_response.inspect}") if response.code != 200
51
+
52
+ DiscordInfo.new(response.parsed_response)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Auth
5
+ class DiscordInfo
6
+ # locale, mfa_enabled, and premium_type are not present for legacy users backfilled from api data,
7
+ # we don't need that data either way
8
+ ATTRIBUTES = %i[id flags avatar banner username global_name public_flags discriminator].freeze
9
+ attr_reader(:data, *ATTRIBUTES)
10
+
11
+ def initialize(options = {}, **kwargs)
12
+ @data = options.merge(kwargs).with_indifferent_access
13
+ ATTRIBUTES.each do |attr|
14
+ instance_variable_set("@#{attr}", @data[attr])
15
+ end
16
+
17
+ return unless YiffSpace.config.auth.update_discord_images
18
+
19
+ last_avatar = Rails.cache.fetch("yiffspace:auth:discord_avatar_update:#{id}")
20
+ last_banner = Rails.cache.fetch("yiffspace:auth:discord_banner_update:#{id}")
21
+
22
+ if last_avatar != avatar
23
+ Rails.cache.write("yiffspace:auth:discord_avatar_update:#{id}", avatar, expires_in: 30.days)
24
+ update_avatar
25
+ end
26
+
27
+ return unless last_banner != banner
28
+
29
+ Rails.cache.write("yiffspace:auth:discord_banner_update:#{id}", banner, expires_in: 30.days)
30
+ update_banner
31
+ end
32
+
33
+ def update_avatar
34
+ Images::Avatar.get_for(id, :discord).update(avatar)
35
+ end
36
+
37
+ def update_banner
38
+ Images::Banner.get_for(id, :discord).update(banner)
39
+ end
40
+
41
+ def serializable_hash(*)
42
+ ATTRIBUTES.to_h { |key| [key.to_s, instance_variable_get("@#{key}")] }
43
+ end
44
+
45
+ def to_session
46
+ serializable_hash
47
+ end
48
+
49
+ def self.from_json(data)
50
+ raise(ArgumentError, "invalid data") if data.blank?
51
+
52
+ data = JSON.parse(data) if data.is_a?(String)
53
+ data = ::YiffSpace::Utils::OpenHash.from(data)
54
+
55
+ new(data)
56
+ end
57
+
58
+ def self.from_session(data)
59
+ return nil if data.blank?
60
+
61
+ from_json(data)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("rails")
4
+
5
+ module YiffSpace
6
+ module Auth
7
+ class Engine < ::Rails::Engine
8
+ isolate_namespace(YiffSpace::Auth)
9
+ config.root = File.expand_path("../../..", __dir__)
10
+
11
+ initializer("yiffspace.auth.serializers") do
12
+ ActiveSupport.on_load(:active_job) do
13
+ ActiveJob::Serializers.add_serializers(
14
+ Serializers::AnonymousAuthInfoSerializer,
15
+ Serializers::AnonymousUserInfoSerializer,
16
+ Serializers::AuthInfoSerializer,
17
+ Serializers::DiscordInfoSerializer,
18
+ Serializers::PermissionsSerializer,
19
+ Serializers::UserInfoSerializer,
20
+ )
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def for(name)
26
+ @instances ||= {}
27
+ @instances[name.to_sym] ||= begin
28
+ subclass = Class.new(self)
29
+ subclass.engine_name("yiffspace_auth_#{name}")
30
+ # Inherit isolation settings that aren't copied from the parent class
31
+ subclass.instance_variable_set(:@isolated, true)
32
+ subclass.routes.default_scope = { module: "yiff_space/auth" }
33
+ subclass.routes.draw do
34
+ constraints(SetClientName.new(name)) do
35
+ post(:webhook, controller: :webhook, action: :create)
36
+ get(:cb, controller: :root)
37
+ get(:logout, controller: :root)
38
+ get(:permissions, controller: :root)
39
+ get(:debug, controller: :root) if ::YiffSpace::Auth.enable_debug_action?
40
+ root(action: :show, controller: :root, as: :auth)
41
+ end
42
+ end
43
+ subclass
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end