masks-server 0.11.0 → 0.12.0
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/app/controllers/masks/server/account_controller.rb +3 -0
- data/app/controllers/masks/server/app_passwords_controller.rb +41 -0
- data/app/controllers/masks/server/authorize_controller.rb +33 -2
- data/app/controllers/masks/server/forward_auth_controller.rb +104 -12
- data/app/controllers/masks/server/logins_controller.rb +4 -3
- data/app/controllers/masks/server/organization_access_controller.rb +39 -0
- data/app/graphql/masks/server/manage/mutations/base_mutation.rb +7 -0
- data/app/graphql/masks/server/manage/mutations/create_group.rb +6 -4
- data/app/graphql/masks/server/manage/mutations/decline_access.rb +27 -0
- data/app/graphql/masks/server/manage/mutations/grant_access.rb +29 -0
- data/app/graphql/masks/server/manage/mutations/group_source.rb +34 -0
- data/app/graphql/masks/server/manage/mutations/issue_ldap_credential.rb +5 -2
- data/app/graphql/masks/server/manage/mutations/refresh_group.rb +21 -0
- data/app/graphql/masks/server/manage/mutations/remove_group_member.rb +2 -0
- data/app/graphql/masks/server/manage/mutations/run_health_checks.rb +19 -0
- data/app/graphql/masks/server/manage/mutations/update_client.rb +6 -0
- data/app/graphql/masks/server/manage/mutations/update_group.rb +7 -4
- data/app/graphql/masks/server/manage/mutations/update_tenant.rb +14 -1
- data/app/graphql/masks/server/manage/types/access_request_type.rb +21 -0
- data/app/graphql/masks/server/manage/types/actor_type.rb +6 -0
- data/app/graphql/masks/server/manage/types/client_type.rb +12 -0
- data/app/graphql/masks/server/manage/types/group_condition_input.rb +16 -0
- data/app/graphql/masks/server/manage/types/group_condition_type.rb +29 -0
- data/app/graphql/masks/server/manage/types/group_member_type.rb +16 -0
- data/app/graphql/masks/server/manage/types/group_type.rb +17 -1
- data/app/graphql/masks/server/manage/types/health_check_type.rb +18 -0
- data/app/graphql/masks/server/manage/types/ldap_credential_type.rb +1 -0
- data/app/graphql/masks/server/manage/types/mutation_type.rb +4 -0
- data/app/graphql/masks/server/manage/types/query_type.rb +24 -0
- data/app/graphql/masks/server/manage/types/release_type.rb +16 -0
- data/app/graphql/masks/server/manage/types/tenant_type.rb +6 -1
- data/app/helpers/masks/server/account_helper.rb +4 -0
- data/app/jobs/masks/server/cleanup_job.rb +1 -0
- data/app/jobs/masks/server/health_check_job.rb +21 -0
- data/app/jobs/masks/server/refresh_groups_job.rb +18 -0
- data/app/jobs/masks/server/release_check_job.rb +21 -0
- data/app/ldap/masks/server/ldap/connection.rb +3 -2
- data/app/ldap/masks/server/ldap/gate.rb +31 -4
- data/app/logins/masks/server/login_states/second_factor.rb +1 -1
- data/app/mailers/masks/server/actor_mailer.rb +31 -0
- data/app/models/masks/server/access_request.rb +103 -0
- data/app/models/masks/server/actor.rb +6 -0
- data/app/models/masks/server/app_password.rb +91 -0
- data/app/models/masks/server/app_passwords.rb +82 -0
- data/app/models/masks/server/client.rb +15 -0
- data/app/models/masks/server/connection.rb +7 -0
- data/app/models/masks/server/declared.rb +10 -2
- data/app/models/masks/server/email_verification.rb +2 -2
- data/app/models/masks/server/event.rb +11 -1
- data/app/models/masks/server/federation/protocol.rb +1 -1
- data/app/models/masks/server/federation/saml.rb +1 -1
- data/app/models/masks/server/forward_auth_rules.rb +162 -0
- data/app/models/masks/server/group.rb +207 -4
- data/app/models/masks/server/group_member.rb +7 -0
- data/app/models/masks/server/group_rule.rb +63 -0
- data/app/models/masks/server/health.rb +243 -0
- data/app/models/masks/server/health_check.rb +27 -0
- data/app/models/masks/server/invitation.rb +2 -2
- data/app/models/masks/server/ldap_credential.rb +2 -2
- data/app/models/masks/server/login.rb +28 -2
- data/app/models/masks/server/mail_previews.rb +5 -0
- data/app/models/masks/server/mail_template.rb +2 -0
- data/app/models/masks/server/membership.rb +10 -3
- data/app/models/masks/server/notifications.rb +3 -1
- data/app/models/masks/server/password_reset.rb +2 -2
- data/app/models/masks/server/provider.rb +1 -0
- data/app/models/masks/server/queue.rb +60 -0
- data/app/models/masks/server/release.rb +74 -0
- data/app/models/masks/server/session.rb +8 -0
- data/app/models/masks/server/tenant.rb +39 -2
- data/app/models/masks/server/token.rb +2 -1
- data/app/views/masks/server/account/_app_passwords.html.erb +83 -0
- data/app/views/masks/server/account/_organizations.html.erb +33 -0
- data/app/views/masks/server/account/index.html.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_declined.html.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_declined.text.erb +5 -0
- data/app/views/masks/server/actor_mailer/access_granted.html.erb +7 -0
- data/app/views/masks/server/actor_mailer/access_granted.text.erb +7 -0
- data/app/views/masks/server/authorize/error.html.erb +25 -0
- data/config/locales/de/access_requests.yml +43 -0
- data/config/locales/de/account/app_passwords.yml +42 -0
- data/config/locales/de/events.yml +5 -0
- data/config/locales/de/flashes.yml +1 -0
- data/config/locales/de/group_sources.yml +5 -0
- data/config/locales/de/manage/access_requests.yml +12 -0
- data/config/locales/de/manage/app_passwords.yml +16 -0
- data/config/locales/de/manage/forward_auth_rules.yml +29 -0
- data/config/locales/de/manage/group_sources.yml +44 -0
- data/config/locales/de/manage/health.yml +78 -0
- data/config/locales/de/manage/links.yml +20 -0
- data/config/locales/en/access_requests.yml +43 -0
- data/config/locales/en/account/app_passwords.yml +42 -0
- data/config/locales/en/events.yml +5 -0
- data/config/locales/en/flashes.yml +1 -0
- data/config/locales/en/group_sources.yml +5 -0
- data/config/locales/en/manage/access_requests.yml +12 -0
- data/config/locales/en/manage/app_passwords.yml +16 -0
- data/config/locales/en/manage/forward_auth_rules.yml +29 -0
- data/config/locales/en/manage/group_sources.yml +44 -0
- data/config/locales/en/manage/health.yml +78 -0
- data/config/locales/en/manage/links.yml +20 -0
- data/config/locales/es/access_requests.yml +43 -0
- data/config/locales/es/account/app_passwords.yml +42 -0
- data/config/locales/es/events.yml +5 -0
- data/config/locales/es/flashes.yml +1 -0
- data/config/locales/es/group_sources.yml +5 -0
- data/config/locales/es/manage/access_requests.yml +12 -0
- data/config/locales/es/manage/app_passwords.yml +16 -0
- data/config/locales/es/manage/forward_auth_rules.yml +29 -0
- data/config/locales/es/manage/group_sources.yml +44 -0
- data/config/locales/es/manage/health.yml +78 -0
- data/config/locales/es/manage/links.yml +20 -0
- data/config/locales/fr/access_requests.yml +43 -0
- data/config/locales/fr/account/app_passwords.yml +42 -0
- data/config/locales/fr/events.yml +5 -0
- data/config/locales/fr/flashes.yml +1 -0
- data/config/locales/fr/group_sources.yml +5 -0
- data/config/locales/fr/manage/access_requests.yml +12 -0
- data/config/locales/fr/manage/app_passwords.yml +16 -0
- data/config/locales/fr/manage/forward_auth_rules.yml +29 -0
- data/config/locales/fr/manage/group_sources.yml +44 -0
- data/config/locales/fr/manage/health.yml +78 -0
- data/config/locales/fr/manage/links.yml +20 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20261020100000_create_access_requests.rb +28 -0
- data/db/migrate/20261020110000_add_app_passwords_to_clients.rb +5 -0
- data/db/migrate/20261020120000_add_forward_auth_rules_to_clients.rb +6 -0
- data/db/migrate/20261020130000_add_sources_to_groups.rb +15 -0
- data/db/migrate/20261020140000_create_health_checks.rb +25 -0
- data/db/migrate/20261020150000_add_link_lifetimes_to_tenants.rb +8 -0
- data/db/migrate/20261021100000_add_pinned_origin_to_tenants.rb +5 -0
- data/lib/generators/masks/server/install/install_generator.rb +3 -2
- data/lib/masks/server/configuration.rb +2 -11
- data/lib/masks/server/schema_reference.rb +12 -0
- data/lib/masks/server/tenancy/middleware.rb +17 -6
- data/public/masks-assets/.vite/manifest.json +13 -13
- data/public/masks-assets/assets/{account-D46qDQCo.js → account-h7I6VUPR.js} +2 -2
- data/public/masks-assets/assets/{account-D46qDQCo.js.map → account-h7I6VUPR.js.map} +1 -1
- data/public/masks-assets/assets/{app-D7W0gAyT.css → app-BXOScMjL.css} +1 -1
- data/public/masks-assets/assets/{handshake-DIFZPSAK.js → handshake-YQpRNMIs.js} +2 -2
- data/public/masks-assets/assets/{handshake-DIFZPSAK.js.map → handshake-YQpRNMIs.js.map} +1 -1
- data/public/masks-assets/assets/initial-Cn-UuAsy.js +3 -0
- data/public/masks-assets/assets/{initial-C1GpCiEh.js.map → initial-Cn-UuAsy.js.map} +1 -1
- data/public/masks-assets/assets/{login-C_6igRZ3.js → login-C21_jb6L.js} +3 -3
- data/public/masks-assets/assets/{login-C_6igRZ3.js.map → login-C21_jb6L.js.map} +1 -1
- data/public/masks-assets/assets/{manage-D4MzaDvM.js → manage-DpVxe5sv.js} +80 -51
- data/public/masks-assets/assets/manage-DpVxe5sv.js.map +1 -0
- data/public/masks-assets/assets/passkey-5UYPVD3c.js +2 -0
- data/public/masks-assets/assets/{passkey-DIj_5LCY.js.map → passkey-5UYPVD3c.js.map} +1 -1
- data/version.txt +1 -1
- metadata +87 -14
- data/public/masks-assets/assets/initial-C1GpCiEh.js +0 -3
- data/public/masks-assets/assets/manage-D4MzaDvM.js.map +0 -1
- data/public/masks-assets/assets/passkey-DIj_5LCY.js +0 -2
|
@@ -18,8 +18,8 @@ module Masks
|
|
|
18
18
|
|
|
19
19
|
before_create { self.invited_at ||= Time.current if pending? }
|
|
20
20
|
|
|
21
|
-
def self.lifetime
|
|
22
|
-
|
|
21
|
+
def self.lifetime(tenant = Current.tenant)
|
|
22
|
+
Tenant.link_lifetime(:organization_invitation_lifetime, tenant)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
normalizes :external_id, with: Actor::EXTERNAL_ID
|
|
@@ -29,13 +29,14 @@ module Masks
|
|
|
29
29
|
validate :an_owner_remains, on: :update
|
|
30
30
|
|
|
31
31
|
before_destroy :keep_an_owner
|
|
32
|
+
after_commit :settle_nested_groups
|
|
32
33
|
|
|
33
34
|
def owner?
|
|
34
35
|
role == Organization::OWNER && !pending?
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def expires_at
|
|
38
|
-
invited_at + self.class.lifetime if pending?
|
|
39
|
+
invited_at + self.class.lifetime(tenant) if pending?
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def expired?
|
|
@@ -72,6 +73,12 @@ module Masks
|
|
|
72
73
|
|
|
73
74
|
private
|
|
74
75
|
|
|
76
|
+
def settle_nested_groups
|
|
77
|
+
return if organization.nil? || actor.nil?
|
|
78
|
+
|
|
79
|
+
Group.nested_under_organization(organization.key).find_each { |group| group.settle!(actor) }
|
|
80
|
+
end
|
|
81
|
+
|
|
75
82
|
def role_is_offered
|
|
76
83
|
errors.add(:role, "is not one #{organization&.name} offers") unless organization&.role?(role)
|
|
77
84
|
end
|
|
@@ -18,7 +18,9 @@ module Masks
|
|
|
18
18
|
Event::TEXT_CODES_ENABLED,
|
|
19
19
|
Event::TEXT_CODES_DISABLED,
|
|
20
20
|
Event::RECOVERY_REQUESTED,
|
|
21
|
-
Event::SECOND_FACTORS_RESET
|
|
21
|
+
Event::SECOND_FACTORS_RESET,
|
|
22
|
+
Event::APP_PASSWORD_CREATED,
|
|
23
|
+
Event::APP_PASSWORD_REVOKED
|
|
22
24
|
],
|
|
23
25
|
"sessions" => [
|
|
24
26
|
Event::SESSION_STARTED,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Queue
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def run?
|
|
7
|
+
::Rails.application.config.active_job.queue_adapter == :solid_queue
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def silence
|
|
11
|
+
::Rails.configuration.masks.worker_silence
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def wait
|
|
15
|
+
::Rails.configuration.masks.queue_wait
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def failing_jobs
|
|
19
|
+
::SolidQueue::FailedExecution.where(created_at: 1.day.ago..).joins(:job)
|
|
20
|
+
.distinct.pluck("solid_queue_jobs.class_name").sort
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def worker_heartbeat_at
|
|
24
|
+
::SolidQueue::Process.where(kind: "Worker").maximum(:last_heartbeat_at)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def oldest_waiting_at
|
|
28
|
+
::SolidQueue::ReadyExecution.minimum(:created_at)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def worker_silent?
|
|
32
|
+
return false unless run? && silence.positive?
|
|
33
|
+
|
|
34
|
+
heartbeat = worker_heartbeat_at
|
|
35
|
+
|
|
36
|
+
heartbeat.nil? || heartbeat < silence.ago
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def stuck_since
|
|
40
|
+
return nil unless run? && wait.positive?
|
|
41
|
+
|
|
42
|
+
oldest = oldest_waiting_at
|
|
43
|
+
|
|
44
|
+
oldest if oldest && oldest < wait.ago
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def plain(duration)
|
|
48
|
+
ActiveSupport::Duration.build(duration.to_i).inspect
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def worker_problem
|
|
52
|
+
return "no worker has reported in the last #{plain(silence)}" if worker_silent?
|
|
53
|
+
|
|
54
|
+
stuck = stuck_since
|
|
55
|
+
|
|
56
|
+
"a job has waited since #{stuck.utc.iso8601}, longer than #{plain(wait)}" if stuck
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Masks
|
|
2
|
+
module Server
|
|
3
|
+
module Release
|
|
4
|
+
URL = "https://api.github.com/repos/masksrb/masks/releases?per_page=20".freeze
|
|
5
|
+
KEY = "masks:release".freeze
|
|
6
|
+
KEEP = 3.days
|
|
7
|
+
TAG = /\Aserver-v(\d+\.\d+\.\d+)\z/
|
|
8
|
+
OPERATOR = /^\s*Operator:/i
|
|
9
|
+
TIMEOUT = 5
|
|
10
|
+
CEILING = 1.megabyte
|
|
11
|
+
UNREADABLE = [ Outbound::Refused, JSON::ParserError, TypeError, NoMethodError ].freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def check!
|
|
16
|
+
found = fetch
|
|
17
|
+
|
|
18
|
+
return nil if found.nil?
|
|
19
|
+
|
|
20
|
+
was = latest
|
|
21
|
+
remember!(found)
|
|
22
|
+
|
|
23
|
+
found if newer?(found["version"]) && was&.dig("version") != found["version"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def fetch
|
|
27
|
+
headers = { "Accept" => "application/vnd.github+json", "User-Agent" => "masks-server/#{VERSION}" }
|
|
28
|
+
body = Outbound.get!(URI(URL), open: TIMEOUT, read: TIMEOUT, within: TIMEOUT, ceiling: CEILING, headers: headers).body
|
|
29
|
+
|
|
30
|
+
JSON.parse(body).each do |release|
|
|
31
|
+
version = release["tag_name"].to_s[TAG, 1]
|
|
32
|
+
|
|
33
|
+
next if version.nil? || release["draft"] || release["prerelease"]
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"version" => version,
|
|
37
|
+
"url" => release["html_url"].to_s,
|
|
38
|
+
"operator_step" => release["body"].to_s.match?(OPERATOR),
|
|
39
|
+
"checked_at" => Time.current.utc.iso8601
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
nil
|
|
44
|
+
rescue *UNREADABLE => error
|
|
45
|
+
::Rails.logger.warn("the release check could not read the releases: #{error.class}")
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def remember!(found)
|
|
50
|
+
::Rails.cache.write(KEY, found, expires_in: KEEP)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def forget!
|
|
54
|
+
::Rails.cache.delete(KEY)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def latest
|
|
58
|
+
::Rails.cache.read(KEY)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def available
|
|
62
|
+
found = latest
|
|
63
|
+
|
|
64
|
+
found if found && newer?(found["version"])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def newer?(version)
|
|
68
|
+
Gem::Version.new(version) > Gem::Version.new(VERSION)
|
|
69
|
+
rescue ArgumentError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -118,6 +118,14 @@ module Masks
|
|
|
118
118
|
true
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
def multi_factored?
|
|
122
|
+
Array(amr).include?(Issuer::MULTI_FACTOR) || !actor.second_factor?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def complete?
|
|
126
|
+
!second_factor_skipped?
|
|
127
|
+
end
|
|
128
|
+
|
|
121
129
|
def fresh?
|
|
122
130
|
authenticated_at.present? && authenticated_at > FRESHNESS.ago
|
|
123
131
|
end
|
|
@@ -64,6 +64,12 @@ module Masks
|
|
|
64
64
|
|
|
65
65
|
IDLE_DAYS = (60..3650)
|
|
66
66
|
RETENTION_DAYS = (30..2555)
|
|
67
|
+
LINK_LIFETIMES = {
|
|
68
|
+
invitation_lifetime: (1.hour.to_i..30.days.to_i),
|
|
69
|
+
organization_invitation_lifetime: (1.hour.to_i..30.days.to_i),
|
|
70
|
+
password_reset_lifetime: (5.minutes.to_i..1.day.to_i),
|
|
71
|
+
email_verification_lifetime: (1.hour.to_i..14.days.to_i)
|
|
72
|
+
}.freeze
|
|
67
73
|
|
|
68
74
|
validates :named_by, inclusion: { in: NAMES }, allow_nil: true
|
|
69
75
|
validates :accent_color, format: { with: ACCENT }, allow_nil: true
|
|
@@ -71,6 +77,18 @@ module Masks
|
|
|
71
77
|
validates :dynamic_registration, inclusion: { in: REGISTRATIONS }, allow_nil: true
|
|
72
78
|
validates :suspend_after, :delete_after, numericality: { only_integer: true, in: IDLE_DAYS }, allow_nil: true
|
|
73
79
|
validates :event_retention_days, numericality: { only_integer: true, in: RETENTION_DAYS }, allow_nil: true
|
|
80
|
+
def self.spoken(seconds)
|
|
81
|
+
unit, length = { "day" => 1.day, "hour" => 1.hour, "minute" => 1.minute }.find { |_, held| (seconds % held).zero? }
|
|
82
|
+
count = seconds / length
|
|
83
|
+
|
|
84
|
+
"#{count} #{unit.pluralize(count)}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
LINK_LIFETIMES.each do |name, range|
|
|
88
|
+
shortest, longest = [ range.begin, range.end ].map { |seconds| spoken(seconds) }
|
|
89
|
+
|
|
90
|
+
validates name, numericality: { only_integer: true, in: range, message: "must be from #{shortest} to #{longest}" }
|
|
91
|
+
end
|
|
74
92
|
validates :delete_after, comparison: { greater_than: :suspend_after, message: "must be longer than suspend after" },
|
|
75
93
|
if: -> { suspend_after && delete_after }
|
|
76
94
|
|
|
@@ -104,8 +122,20 @@ module Masks
|
|
|
104
122
|
event_retention_days&.days || Event::RETENTION
|
|
105
123
|
end
|
|
106
124
|
|
|
125
|
+
def self.link_lifetime(name, tenant = Current.tenant)
|
|
126
|
+
(tenant ? tenant[name] : column_defaults.fetch(name.to_s)).seconds
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.default_link_lifetime(name)
|
|
130
|
+
column_defaults.fetch(name.to_s)
|
|
131
|
+
end
|
|
132
|
+
|
|
107
133
|
def public_origin
|
|
108
|
-
custom_origin || templated_origin
|
|
134
|
+
custom_origin || templated_origin || held_origin
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def held_origin
|
|
138
|
+
pinned_origin if self.class.pins_origin?
|
|
109
139
|
end
|
|
110
140
|
|
|
111
141
|
def templated_origin
|
|
@@ -246,6 +276,10 @@ module Masks
|
|
|
246
276
|
serving(host) || named(host)
|
|
247
277
|
end
|
|
248
278
|
|
|
279
|
+
def pins_origin?
|
|
280
|
+
::Rails.configuration.masks.public_origin_template.nil? && ::Rails.configuration.masks.pin_origin
|
|
281
|
+
end
|
|
282
|
+
|
|
249
283
|
def served_domain
|
|
250
284
|
template = ::Rails.configuration.masks.public_origin_template
|
|
251
285
|
|
|
@@ -403,7 +437,10 @@ module Masks
|
|
|
403
437
|
end
|
|
404
438
|
|
|
405
439
|
def set_up!
|
|
406
|
-
|
|
440
|
+
self.setup_token = nil
|
|
441
|
+
self.pinned_origin ||= Current.origin.presence if self.class.pins_origin?
|
|
442
|
+
|
|
443
|
+
save! if changed?
|
|
407
444
|
end
|
|
408
445
|
|
|
409
446
|
def setup_announcement
|
|
@@ -22,7 +22,8 @@ module Masks
|
|
|
22
22
|
"pushed" => "PushedRequest",
|
|
23
23
|
"approval" => "SignInApproval",
|
|
24
24
|
"forward_code" => "ForwardCode",
|
|
25
|
-
"forward_session" => "ForwardSession"
|
|
25
|
+
"forward_session" => "ForwardSession",
|
|
26
|
+
"app_password" => "AppPassword"
|
|
26
27
|
}.freeze
|
|
27
28
|
|
|
28
29
|
self.inheritance_column = "kind"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<section class="group" id="app-passwords">
|
|
2
|
+
<h2><%= account_text("app_passwords.heading") %></h2>
|
|
3
|
+
|
|
4
|
+
<div class="entries">
|
|
5
|
+
<details class="entry" <%= "open" if app_password %>>
|
|
6
|
+
<summary>
|
|
7
|
+
<span class="entry-icon"><%= account_icon(:password) %></span>
|
|
8
|
+
<span class="entry-label"><%= account_text("app_passwords.entry") %></span>
|
|
9
|
+
<span class="entry-state">
|
|
10
|
+
<%= app_passwords.any? ? app_passwords.size : account_text("app_passwords.state.none") %>
|
|
11
|
+
</span>
|
|
12
|
+
<span class="entry-mark" aria-hidden="true"></span>
|
|
13
|
+
</summary>
|
|
14
|
+
|
|
15
|
+
<div class="entry-body">
|
|
16
|
+
<p class="aside"><%= account_text("app_passwords.lede") %></p>
|
|
17
|
+
|
|
18
|
+
<% if app_password %>
|
|
19
|
+
<p class="aside aside-bad" role="alert">
|
|
20
|
+
<%= account_text("app_passwords.shown_once", name: app_password["name"], app: app_password["app"]) %>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<ol class="codes">
|
|
24
|
+
<li><code><%= app_password["secret"] %></code></li>
|
|
25
|
+
</ol>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<% if app_passwords.any? %>
|
|
29
|
+
<div class="items">
|
|
30
|
+
<% app_passwords.each do |held| %>
|
|
31
|
+
<div class="item">
|
|
32
|
+
<span class="item-name">
|
|
33
|
+
<span><%= held.name %></span>
|
|
34
|
+
<span class="tag"><%= held.client.name %></span>
|
|
35
|
+
</span>
|
|
36
|
+
|
|
37
|
+
<span class="item-meta">
|
|
38
|
+
<%= account_text("app_passwords.made_ago", age: time_ago_in_words(held.created_at)) %>
|
|
39
|
+
·
|
|
40
|
+
<% if held.used_at %>
|
|
41
|
+
<%= account_text("app_passwords.used_ago", age: time_ago_in_words(held.used_at)) %>
|
|
42
|
+
<% else %>
|
|
43
|
+
<%= account_text("app_passwords.never_used") %>
|
|
44
|
+
<% end %>
|
|
45
|
+
</span>
|
|
46
|
+
|
|
47
|
+
<%= button_to account_text("app_passwords.revoke"),
|
|
48
|
+
account_app_password_path(held.id), method: :delete,
|
|
49
|
+
class: "action action-quiet action-sm", form_class: "item-act",
|
|
50
|
+
form: { data: { turbo_confirm: account_text("app_passwords.confirm_revoke", name: held.name) } } %>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
|
|
56
|
+
<% if offered.any? %>
|
|
57
|
+
<%= form_with url: account_app_passwords_path, method: :post, class: "member-add" do |form| %>
|
|
58
|
+
<label class="field member-add-email">
|
|
59
|
+
<span class="field-label"><%= account_text("app_passwords.name") %></span>
|
|
60
|
+
<%= form.text_field :name, required: true, autocomplete: "off", class: "control", id: "app-password-name",
|
|
61
|
+
maxlength: Masks::Server::AppPassword::NAME_LIMIT,
|
|
62
|
+
placeholder: account_text("app_passwords.name_placeholder") %>
|
|
63
|
+
</label>
|
|
64
|
+
|
|
65
|
+
<label class="field member-add-role">
|
|
66
|
+
<span class="field-label"><%= account_text("app_passwords.app") %></span>
|
|
67
|
+
<%= form.select :client_id, offered.map { |client| [ client.name, client.client_id ] }, {}, class: "control",
|
|
68
|
+
id: "app-password-client" %>
|
|
69
|
+
</label>
|
|
70
|
+
|
|
71
|
+
<button type="submit" class="action action-sm member-add-go">
|
|
72
|
+
<%= account_text(fresh ? "app_passwords.make" : "app_passwords.make_again") %>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
<span class="field-hint member-add-hint"><%= account_text("app_passwords.hint") %></span>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% else %>
|
|
78
|
+
<p class="aside"><%= account_text("app_passwords.none_offered") %></p>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
81
|
+
</details>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
@@ -135,6 +135,39 @@
|
|
|
135
135
|
<% end %>
|
|
136
136
|
</div>
|
|
137
137
|
|
|
138
|
+
<% asked = account_access_requests(organization) %>
|
|
139
|
+
<% if asked.any? %>
|
|
140
|
+
<p class="aside"><strong><%= account_text("access.heading") %></strong> <%= account_text("access.lede", organization: organization.name) %></p>
|
|
141
|
+
|
|
142
|
+
<div class="items" id="access-requests-<%= organization.key %>">
|
|
143
|
+
<% asked.each do |request| %>
|
|
144
|
+
<% roles = request.organization_roles.filter_map { |key, role| role if key == organization.key } %>
|
|
145
|
+
<div class="item item-member access-request" id="access-request-<%= request.id %>">
|
|
146
|
+
<span class="item-name"><%= request.actor.identifier %></span>
|
|
147
|
+
<span class="item-meta">
|
|
148
|
+
<%= account_text("access.asked", app: request.client.name, age: time_ago_in_words(request.created_at)) %>
|
|
149
|
+
</span>
|
|
150
|
+
|
|
151
|
+
<div class="item-act item-acts">
|
|
152
|
+
<%= form_with url: account_organization_access_path(organization.key, request.id), method: :post,
|
|
153
|
+
class: "item-acts" do |form| %>
|
|
154
|
+
<%= form.select :role, roles, {}, class: "control control-sm",
|
|
155
|
+
id: "access-role-#{organization.key}-#{request.id}",
|
|
156
|
+
aria: { label: account_text("access.role_of", name: request.actor.identifier) } %>
|
|
157
|
+
<button type="submit" class="action action-sm"><%= account_text("access.grant") %></button>
|
|
158
|
+
<% end %>
|
|
159
|
+
|
|
160
|
+
<%= button_to account_text("access.decline"),
|
|
161
|
+
account_organization_access_path(organization.key, request.id), method: :delete,
|
|
162
|
+
class: "action action-quiet action-sm",
|
|
163
|
+
form: { data: { turbo_confirm: account_text("access.confirm_decline", identifier: request.actor.identifier,
|
|
164
|
+
app: request.client.name) } } %>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<% end %>
|
|
168
|
+
</div>
|
|
169
|
+
<% end %>
|
|
170
|
+
|
|
138
171
|
<%= form_with url: account_organization_members_path(organization.key), method: :post, class: "member-add" do |form| %>
|
|
139
172
|
<label class="field member-add-email">
|
|
140
173
|
<span class="field-label"><%= account_text("organizations.add_email") %></span>
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
|
|
32
32
|
<%= render "masks/server/account/apps", apps: @apps, connections: @connections, linkable: @linkable %>
|
|
33
33
|
|
|
34
|
+
<% if @app_passwords.any? || @app_password_clients.any? %>
|
|
35
|
+
<%= render "masks/server/account/app_passwords", app_passwords: @app_passwords, offered: @app_password_clients,
|
|
36
|
+
app_password: @app_password, fresh: @fresh %>
|
|
37
|
+
<% end %>
|
|
38
|
+
|
|
34
39
|
<%= render "masks/server/account/info", actor: @actor, directed: @directed, fresh: @fresh %>
|
|
35
40
|
|
|
36
41
|
<%= render "masks/server/account/activity", events: @events %>
|
|
@@ -9,4 +9,29 @@
|
|
|
9
9
|
<p class="said"><%= @error_code %> — <%= @error_description %></p>
|
|
10
10
|
|
|
11
11
|
<p class="prompt-lede"><%= @refused ? t(".nothing_shared") : t(".contact") %></p>
|
|
12
|
+
|
|
13
|
+
<% if @asking %>
|
|
14
|
+
<div id="ask-access" class="flow" data-waiting="<%= @waiting.present? %>">
|
|
15
|
+
<% if flash[:alert].present? %>
|
|
16
|
+
<p class="note note-bad" role="alert"><%= flash[:alert] %></p>
|
|
17
|
+
<% elsif flash[:notice].present? %>
|
|
18
|
+
<p class="note note-plain" role="status"><%= flash[:notice] %></p>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<% if @waiting %>
|
|
22
|
+
<p class="prompt-lede"><%= t(".waiting", app: @asking.name) %></p>
|
|
23
|
+
<% else %>
|
|
24
|
+
<p class="prompt-lede"><%= t(".asking", app: @asking.name) %></p>
|
|
25
|
+
|
|
26
|
+
<%= form_with url: authorize_access_request_path, method: :post do |form| %>
|
|
27
|
+
<%= form.hidden_field :rid, value: params[:rid] %>
|
|
28
|
+
<button type="submit" class="action"><%= t(".ask") %></button>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
|
|
34
|
+
<% if @return_to %>
|
|
35
|
+
<a class="action action-quiet" id="back-to-app" href="<%= @return_to %>"><%= t(".back", app: @asking&.name || t(".the_app")) %></a>
|
|
36
|
+
<% end %>
|
|
12
37
|
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
de:
|
|
2
|
+
authorize:
|
|
3
|
+
error:
|
|
4
|
+
asking: "Ihr Konto ist in keiner Gruppe, die %{app} nutzen darf. Sie können um Zugang bitten. Die Betreiber von %{app} entscheiden, und Sie erhalten in jedem Fall eine E-Mail."
|
|
5
|
+
ask: "Um Zugang bitten"
|
|
6
|
+
asked: "Ihre Anfrage wurde gesendet. Sie erhalten eine E-Mail, sobald sie entschieden ist."
|
|
7
|
+
waiting: "Ihre Anfrage, %{app} zu nutzen, wartet. Sie erhalten eine E-Mail, sobald sie entschieden ist."
|
|
8
|
+
slow_down: "Sie haben vor weniger als einer Stunde um Zugang zu dieser App gebeten. Warten Sie auf die Antwort."
|
|
9
|
+
back: "Zurück zu %{app}"
|
|
10
|
+
the_app: "der App"
|
|
11
|
+
|
|
12
|
+
account:
|
|
13
|
+
index:
|
|
14
|
+
access:
|
|
15
|
+
heading: "Um Zugang gebeten"
|
|
16
|
+
lede: "Diese Personen wurden von einer App abgewiesen, die Mitglieder von %{organization} zulässt. Mit Genehmigen werden sie Mitglied mit der Rolle, die Sie wählen."
|
|
17
|
+
asked: "Hat vor %{age} gebeten, %{app} zu nutzen"
|
|
18
|
+
role_of: "Rolle für %{name}"
|
|
19
|
+
grant: "Genehmigen"
|
|
20
|
+
decline: "Ablehnen"
|
|
21
|
+
confirm_decline: "Die Anfrage von %{identifier}, %{app} zu nutzen, ablehnen? Die Person erhält eine E-Mail und kann in einer Stunde erneut bitten."
|
|
22
|
+
|
|
23
|
+
organization_access:
|
|
24
|
+
granted: "%{identifier} ist jetzt %{role} von %{organization} und kann %{app} nutzen."
|
|
25
|
+
declined: "Die Anfrage von %{identifier}, %{app} zu nutzen, ist abgelehnt."
|
|
26
|
+
unknown: "Diese Anfrage wartet nicht auf %{organization}."
|
|
27
|
+
|
|
28
|
+
actor_mailer:
|
|
29
|
+
access_granted:
|
|
30
|
+
subject: "Sie können %{app} jetzt nutzen"
|
|
31
|
+
lede: "Ihre Anfrage, %{app} bei %{tenant} zu nutzen, wurde genehmigt. Melden Sie sich erneut bei %{app} an, und Sie werden eingelassen."
|
|
32
|
+
action: "%{app} öffnen"
|
|
33
|
+
access_declined:
|
|
34
|
+
subject: "Ihre Anfrage, %{app} zu nutzen, wurde abgelehnt"
|
|
35
|
+
lede: "Jemand, der %{app} bei %{tenant} betreibt, hat Ihre Anfrage um Zugang abgelehnt."
|
|
36
|
+
again: "Wenn Sie das für einen Fehler halten, fragen Sie die Person direkt. In einer Stunde können Sie erneut bitten."
|
|
37
|
+
|
|
38
|
+
events:
|
|
39
|
+
actions:
|
|
40
|
+
access:
|
|
41
|
+
requested: "Um Zugang zu einer App gebeten"
|
|
42
|
+
granted: "Zugang zu einer App genehmigt"
|
|
43
|
+
declined: "Zugang zu einer App abgelehnt"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
de:
|
|
2
|
+
account:
|
|
3
|
+
index:
|
|
4
|
+
app_passwords:
|
|
5
|
+
heading: "App-Passwörter"
|
|
6
|
+
entry: "App-Passwörter"
|
|
7
|
+
state:
|
|
8
|
+
none: "Keine"
|
|
9
|
+
lede: "Mit einem App-Passwort meldet sich eine einzelne App, etwa ein Medienserver oder ein NAS, ohne Ihren zweiten Faktor als Sie an. Es gilt nur für die App, für die es erstellt wurde."
|
|
10
|
+
shown_once: "Das ist das App-Passwort für %{name}. Es wird nur einmal angezeigt. Geben Sie es jetzt in %{app} ein."
|
|
11
|
+
made_ago: "erstellt vor %{age}"
|
|
12
|
+
used_ago: "zuletzt verwendet vor %{age}"
|
|
13
|
+
never_used: "nie verwendet"
|
|
14
|
+
revoke: "Widerrufen"
|
|
15
|
+
confirm_revoke: "%{name} widerrufen? Die App kann sich damit sofort nicht mehr anmelden."
|
|
16
|
+
name: "Name"
|
|
17
|
+
name_placeholder: "Jellyfin auf dem Fernseher"
|
|
18
|
+
app: "App"
|
|
19
|
+
make: "App-Passwort erstellen"
|
|
20
|
+
make_again: "Zum Erstellen erneut anmelden"
|
|
21
|
+
hint: "Ihr eigenes Passwort und Ihre Passkeys bleiben unverändert. Das Widerrufen eines App-Passworts ändert sonst nichts."
|
|
22
|
+
none_offered: "Noch keine App akzeptiert App-Passwörter."
|
|
23
|
+
app_passwords:
|
|
24
|
+
again: "Melden Sie sich erneut an, um ein App-Passwort zu erstellen. Ihre letzte Anmeldung liegt eine Weile zurück."
|
|
25
|
+
unknown: "Dieses Konto hat kein solches App-Passwort."
|
|
26
|
+
revoked: "%{name} ist widerrufen."
|
|
27
|
+
refused:
|
|
28
|
+
name: "Geben Sie dem App-Passwort einen Namen mit höchstens 64 Zeichen."
|
|
29
|
+
app: "Diese App akzeptiert keine App-Passwörter."
|
|
30
|
+
too_many: "Sie haben bereits %{most} App-Passwörter. Widerrufen Sie zuerst eines."
|
|
31
|
+
events:
|
|
32
|
+
actions:
|
|
33
|
+
app_password:
|
|
34
|
+
created: "App-Passwort erstellt"
|
|
35
|
+
used: "Mit einem App-Passwort angemeldet"
|
|
36
|
+
revoked: "App-Passwort widerrufen"
|
|
37
|
+
actor_mailer:
|
|
38
|
+
notification:
|
|
39
|
+
said:
|
|
40
|
+
app_password:
|
|
41
|
+
created: "Für Ihr %{tenant}-Konto wurde ein App-Passwort erstellt. Damit meldet sich eine App ohne Ihren zweiten Faktor an."
|
|
42
|
+
revoked: "Ein App-Passwort Ihres %{tenant}-Kontos wurde widerrufen."
|
|
@@ -159,6 +159,11 @@ de:
|
|
|
159
159
|
updated: "E-Mail-Vorlage geändert"
|
|
160
160
|
job:
|
|
161
161
|
failed: "Hintergrundjob für diesen Mandanten fehlgeschlagen"
|
|
162
|
+
health:
|
|
163
|
+
failing: "Zustandsprüfung fehlgeschlagen"
|
|
164
|
+
recovered: "Zustandsprüfung wieder in Ordnung"
|
|
165
|
+
release:
|
|
166
|
+
available: "Neue Version verfügbar"
|
|
162
167
|
stream:
|
|
163
168
|
created: "Event-Stream hinzugefügt"
|
|
164
169
|
updated: "Event-Stream geändert"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
de:
|
|
2
|
+
manage:
|
|
3
|
+
access_requests:
|
|
4
|
+
title: "Zugangsanfragen"
|
|
5
|
+
lede: "Akteure, die von diesem Client abgewiesen wurden und um Zugang gebeten haben. Mit Genehmigen kommen sie in eine der Gruppen, die er zulässt, und sie erhalten in jedem Fall eine E-Mail."
|
|
6
|
+
empty: "Niemand wartet auf Zugang."
|
|
7
|
+
asked: "Gebeten %{day}"
|
|
8
|
+
as: "Gruppe, in die sie kommen"
|
|
9
|
+
grant: "Genehmigen"
|
|
10
|
+
decline: "Ablehnen"
|
|
11
|
+
granted: "%{identifier} ist jetzt in %{label} und kann sich anmelden."
|
|
12
|
+
declined: "Die Anfrage von %{identifier} ist abgelehnt."
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
de:
|
|
2
|
+
manage:
|
|
3
|
+
client:
|
|
4
|
+
sign_in:
|
|
5
|
+
app_passwords: "App-Passwörter akzeptieren"
|
|
6
|
+
app_passwords_help: "Akteure erstellen auf ihrer Kontoseite ein App-Passwort für diesen Client. Es meldet sie über LDAP und Forward-Auth ohne zweiten Faktor an, nur bei diesem Client."
|
|
7
|
+
notices:
|
|
8
|
+
app_passwords_on: "Akteure können jetzt ein App-Passwort dafür erstellen."
|
|
9
|
+
app_passwords_off: "Seine App-Passwörter werden ab jetzt abgelehnt."
|
|
10
|
+
ldap:
|
|
11
|
+
connect:
|
|
12
|
+
app: "App"
|
|
13
|
+
no_app: "Keine App"
|
|
14
|
+
app_help: "Wählen Sie den Client, als der der Dienst registriert ist. App-Passwörter der Akteure für diesen Client binden dann auf einer Verbindung, die sich zuerst mit diesem Zugang gebunden hat."
|
|
15
|
+
credentials:
|
|
16
|
+
app: "Akzeptiert App-Passwörter für %{name}"
|