From fa7e64df1de13b361551b0ff8f81335719c1a4a2 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 27 Oct 2023 01:37:58 +0200 Subject: [PATCH 01/13] Fix various icon styles in web UI (#27579) --- .../features/account/components/header.jsx | 6 ++--- .../mastodon/features/audio/index.jsx | 6 ++--- .../features/status/components/card.jsx | 4 ++-- .../mastodon/features/video/index.jsx | 6 ++--- .../styles/mastodon/components.scss | 22 ++++++++++++++++++- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index 3ecfb8a2bbf207..76074225adf5c2 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -12,8 +12,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { ReactComponent as CheckIcon } from '@material-symbols/svg-600/outlined/check.svg'; import { ReactComponent as LockIcon } from '@material-symbols/svg-600/outlined/lock.svg'; import { ReactComponent as MoreHorizIcon } from '@material-symbols/svg-600/outlined/more_horiz.svg'; -import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications-fill.svg'; -import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active.svg'; +import { ReactComponent as NotificationsIcon } from '@material-symbols/svg-600/outlined/notifications.svg'; +import { ReactComponent as NotificationsActiveIcon } from '@material-symbols/svg-600/outlined/notifications_active-fill.svg'; import { Avatar } from 'mastodon/components/avatar'; import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge'; @@ -264,7 +264,7 @@ class Header extends ImmutablePureComponent { } if (account.getIn(['relationship', 'requested']) || account.getIn(['relationship', 'following'])) { - bellBtn = ; + bellBtn = ; } if (me !== account.get('id')) { diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx index 60d599b97a8d6b..7a7d0910fa8f70 100644 --- a/app/javascript/mastodon/features/audio/index.jsx +++ b/app/javascript/mastodon/features/audio/index.jsx @@ -9,10 +9,10 @@ import { is } from 'immutable'; import { ReactComponent as DownloadIcon } from '@material-symbols/svg-600/outlined/download.svg'; import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; +import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up.svg'; +import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; +import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle, debounce } from 'lodash'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index 684c599adb0d55..d7d688952d6405 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -10,9 +10,9 @@ import classNames from 'classnames'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description.svg'; +import { ReactComponent as DescriptionIcon } from '@material-symbols/svg-600/outlined/description-fill.svg'; import { ReactComponent as OpenInNewIcon } from '@material-symbols/svg-600/outlined/open_in_new.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; +import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx index c04d9e3d4b9085..05b1316fd06b48 100644 --- a/app/javascript/mastodon/features/video/index.jsx +++ b/app/javascript/mastodon/features/video/index.jsx @@ -10,11 +10,11 @@ import { is } from 'immutable'; import { ReactComponent as FullscreenIcon } from '@material-symbols/svg-600/outlined/fullscreen.svg'; import { ReactComponent as FullscreenExitIcon } from '@material-symbols/svg-600/outlined/fullscreen_exit.svg'; import { ReactComponent as PauseIcon } from '@material-symbols/svg-600/outlined/pause.svg'; -import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow.svg'; +import { ReactComponent as PlayArrowIcon } from '@material-symbols/svg-600/outlined/play_arrow-fill.svg'; import { ReactComponent as RectangleIcon } from '@material-symbols/svg-600/outlined/rectangle.svg'; import { ReactComponent as VisibilityOffIcon } from '@material-symbols/svg-600/outlined/visibility_off.svg'; -import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off.svg'; -import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up.svg'; +import { ReactComponent as VolumeOffIcon } from '@material-symbols/svg-600/outlined/volume_off-fill.svg'; +import { ReactComponent as VolumeUpIcon } from '@material-symbols/svg-600/outlined/volume_up-fill.svg'; import { throttle } from 'lodash'; import { Blurhash } from 'mastodon/components/blurhash'; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 417f262a280a81..b8e6af0aaaf231 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7434,7 +7434,12 @@ noscript { border: 1px solid lighten($ui-base-color, 12%); border-radius: 4px; box-sizing: content-box; - padding: 2px; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } } } @@ -7517,6 +7522,11 @@ noscript { color: lighten($ui-highlight-color, 8%); } + .icon { + width: 18px; + height: 18px; + } + .verified { border: 1px solid rgba($valid-value-color, 0.5); margin-top: -1px; @@ -7537,6 +7547,16 @@ noscript { color: $valid-value-color; } + dd { + display: flex; + align-items: center; + gap: 4px; + + span { + display: flex; + } + } + a { color: $valid-value-color; } From 8f998cd96a1512b545cf674edad012839cec5cb2 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Fri, 27 Oct 2023 11:36:22 +0900 Subject: [PATCH 02/13] Handle featured collections without items (#27581) --- .../activitypub/fetch_featured_collection_service.rb | 2 ++ .../fetch_featured_collection_service_spec.rb | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index e8a31dade9b07b..d2bae08a0e4fdc 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -37,6 +37,8 @@ def fetch_collection(collection_or_uri) end def process_items(items) + return if items.nil? + process_note_items(items) if @options[:note] process_hashtag_items(items) if @options[:hashtag] end diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb index 5975c81a1018ac..466da891a8bbce 100644 --- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb @@ -42,12 +42,22 @@ } end + let(:featured_with_null) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'https://example.com/account/collections/featured', + totalItems: 0, + type: 'OrderedCollection', + } + end + let(:items) do [ 'https://example.com/account/pinned/known', # known status_json_pinned_unknown_inlined, # unknown inlined 'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable 'https://example.com/account/pinned/unknown-reachable', # unknown reachable + 'https://example.com/account/collections/featured', # featured with null ] end @@ -66,6 +76,7 @@ stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined)) stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404) stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_unreachable)) + stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null)) subject.call(actor, note: true, hashtag: false) end From bbf46cc41833d14d9bda7dcfd37dc188861987da Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 27 Oct 2023 10:35:21 +0200 Subject: [PATCH 03/13] Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts (#27459) --- .../rest/featured_tag_serializer.rb | 4 +- config/routes.rb | 2 +- .../accounts/featured_tags_controller_spec.rb | 23 --------- .../api/v1/accounts/featured_tags_spec.rb | 50 +++++++++++++++++++ 4 files changed, 54 insertions(+), 25 deletions(-) delete mode 100644 spec/controllers/api/v1/accounts/featured_tags_controller_spec.rb create mode 100644 spec/requests/api/v1/accounts/featured_tags_spec.rb diff --git a/app/serializers/rest/featured_tag_serializer.rb b/app/serializers/rest/featured_tag_serializer.rb index c4b35ab03ab96f..c1ff4602aa83f6 100644 --- a/app/serializers/rest/featured_tag_serializer.rb +++ b/app/serializers/rest/featured_tag_serializer.rb @@ -10,7 +10,9 @@ def id end def url - short_account_tag_url(object.account, object.tag) + # The path is hardcoded because we have to deal with both local and + # remote users, which are different routes + account_with_domain_url(object.account, "tagged/#{object.tag.to_param}") end def name diff --git a/config/routes.rb b/config/routes.rb index 89c147869a7421..ce3fd9596a3277 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,7 +134,7 @@ get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status end - get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, format: false + get '/@:username_with_domain/(*any)', to: 'home#index', constraints: { username_with_domain: %r{([^/])+?} }, as: :account_with_domain, format: false get '/settings', to: redirect('/settings/profile') draw(:settings) diff --git a/spec/controllers/api/v1/accounts/featured_tags_controller_spec.rb b/spec/controllers/api/v1/accounts/featured_tags_controller_spec.rb deleted file mode 100644 index 53ac1e2a7a23c0..00000000000000 --- a/spec/controllers/api/v1/accounts/featured_tags_controller_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Accounts::FeaturedTagsController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } - let(:account) { Fabricate(:account) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } - - expect(response).to have_http_status(200) - end - end -end diff --git a/spec/requests/api/v1/accounts/featured_tags_spec.rb b/spec/requests/api/v1/accounts/featured_tags_spec.rb new file mode 100644 index 00000000000000..bae7d448b6daee --- /dev/null +++ b/spec/requests/api/v1/accounts/featured_tags_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'account featured tags API' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + let(:account) { Fabricate(:account) } + + describe 'GET /api/v1/accounts/:id/featured_tags' do + subject do + get "/api/v1/accounts/#{account.id}/featured_tags", headers: headers + end + + before do + account.featured_tags.create!(name: 'foo') + account.featured_tags.create!(name: 'bar') + end + + it 'returns the expected tags', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to contain_exactly(a_hash_including({ + name: 'bar', + url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/bar", + }), a_hash_including({ + name: 'foo', + url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/foo", + })) + end + + context 'when the account is remote' do + it 'returns the expected tags', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to contain_exactly(a_hash_including({ + name: 'bar', + url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/bar", + }), a_hash_including({ + name: 'foo', + url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/foo", + })) + end + end + end +end From 8ca16f032e8347935ad6c03b5d97a8d7a3e367b7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:38:04 +0200 Subject: [PATCH 04/13] New Crowdin Translations (automated) (#27583) Co-authored-by: GitHub Actions --- config/locales/de.yml | 2 ++ config/locales/es-AR.yml | 8 ++++++++ config/locales/es.yml | 8 ++++++-- config/locales/fo.yml | 8 ++++++++ config/locales/fr-QC.yml | 8 ++++++++ config/locales/fr.yml | 8 ++++++++ config/locales/fy.yml | 8 ++++++++ config/locales/he.yml | 8 ++++++++ config/locales/hu.yml | 10 +++++----- config/locales/is.yml | 4 ++++ config/locales/it.yml | 8 ++++++++ config/locales/nl.yml | 8 ++++++++ config/locales/pt-BR.yml | 2 ++ config/locales/pt-PT.yml | 8 ++++++++ config/locales/ru.yml | 10 ++++++++++ config/locales/sv.yml | 3 ++- config/locales/th.yml | 8 ++++++++ config/locales/tr.yml | 8 ++++++++ config/locales/uk.yml | 8 ++++++++ 19 files changed, 127 insertions(+), 8 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 21dfe4a7fb106e..ca7659c4c9343c 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1043,8 +1043,10 @@ de: confirmations: awaiting_review: Deine E-Mail-Adresse wurde bestätigt und das Team von %{domain} überprüft nun deine Registrierung. Sobald es dein Konto genehmigt, wirst du eine E-Mail erhalten. awaiting_review_title: Deine Registrierung wird überprüft + clicking_this_link: Klick auf diesen Link login_link: anmelden proceed_to_login_html: Du kannst dich nun %{login_link}. + redirect_to_app_html: Du hättest zur %{app_name}-App weitergeleitet werden sollen. Wenn das nicht geschehen ist, versuche es mit einem %{clicking_this_link} oder kehre manuell zur App zurück. registration_complete: Deine Registrierung auf %{domain} ist nun abgeschlossen! welcome_title: Willkommen, %{name}! wrong_email_hint: Sollte diese E-Mail-Adresse nicht korrekt sein, kannst du sie in den Kontoeinstellungen ändern. diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index fcff9469100c82..127f1262a3092d 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1041,6 +1041,14 @@ es-AR: hint_html: ¡Sólo una cosa más! Necesitamos confirmar que sos humano (¡esto es para que podamos mantener el spam fuera!). Resuelvé la CAPTCHA abajo y hacé clic en "Continuar". title: Comprobación de seguridad confirmations: + awaiting_review: "¡Tu dirección de correo electrónico fue confirmada! El equipo de %{domain} está revisando tu registro. ¡Recibirás un correo electrónico si aprueban tu cuenta!" + awaiting_review_title: Tu registro está siendo revisado + clicking_this_link: haciendo clic en este enlace + login_link: iniciar sesión + proceed_to_login_html: Ahora podés %{login_link}. + redirect_to_app_html: Deberías haber sido redirigido a la aplicación %{app_name}. Si eso no sucedió, probá %{clicking_this_link} o volvé a la aplicación manualmente. + registration_complete: "¡Tu registro en %{domain} fue completado!" + welcome_title: "¡Te damos la bienvenida, %{name}!" wrong_email_hint: Si esa dirección de correo electrónico no es correcta, podés cambiarla en la configuración de la cuenta. delete_account: Eliminar cuenta delete_account_html: Si querés eliminar tu cuenta, podés seguir por acá. Se te va a pedir una confirmación. diff --git a/config/locales/es.yml b/config/locales/es.yml index 5dce6330d6c3f7..aab31747e7655a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1041,6 +1041,10 @@ es: hint_html: ¡Una última cosita! Necesitamos confirmar que eres humano (¡así podemos evitar el spam!). Resuelve este CAPTCHA de debajo y pulsa en "Continuar". title: Comprobación de seguridad confirmations: + awaiting_review: "¡Tu dirección de correo electrónico ha sido confirmada! El personal de %{domain} está revisando tu registro. ¡Recibirás un correo electrónico cuando aprueben tu cuenta!" + awaiting_review_title: Estamos revisando tu registro + clicking_this_link: haciendo clic en este enlace + registration_complete: "¡Has completado tu registro en %{domain}!" wrong_email_hint: Si esa dirección de correo electrónico no es correcta, puedes cambiarla en la configuración de la cuenta. delete_account: Borrar cuenta delete_account_html: Si desea eliminar su cuenta, puede proceder aquí. Será pedido de una confirmación. @@ -1100,7 +1104,7 @@ es: account_status: Estado de la cuenta confirming: Esperando confirmación de correo electrónico. functional: Tu cuenta está completamente operativa. - pending: Su solicitud está pendiente de revisión por nuestros administradores. Eso puede tardar algún tiempo. Usted recibirá un correo electrónico si el solicitud sea aprobada. + pending: Tu solicitud está pendiente de revisión por nuestro personal. Eso puede tardar un tiempo. Recibirás un correo electrónico cuando tu solicitud sea aprobada. redirecting_to: Tu cuenta se encuentra inactiva porque está siendo redirigida a %{acct}. self_destruct: Como %{domain} está en proceso de cierre, solo tendrás acceso limitado a tu cuenta. view_strikes: Ver amonestaciones pasadas contra tu cuenta @@ -1786,7 +1790,7 @@ es: title: Un nuevo inicio de sesión warning: appeal: Enviar una apelación - appeal_description: Si crees que esto es un error, puedes enviar una apelación al equipo de %{instance}. + appeal_description: Si crees que esto es un error, puedes enviar una apelación al personal de %{instance}. categories: spam: Spam violation: El contenido viola las siguientes directrices de la comunidad diff --git a/config/locales/fo.yml b/config/locales/fo.yml index bd716fa3846fc3..8e98cc86490629 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1041,6 +1041,14 @@ fo: hint_html: Bara eitt afturat! Tað er neyðugt hjá okkum at vátta, at tú ert eitt menniskja (fyri at sleppa undan ruskposti!). Loys CAPTCHA niðanfyri og trýst á "Halt fram". title: Trygdarkanning confirmations: + awaiting_review: Teldupostadressan hjá góðkend! Nú kanna %{domain} ábyrgdarfólk skrásetingina hjá tær. Góðkenna tey kontu tína, so senda tey tær eitt teldubræv! + awaiting_review_title: Skrásetingin hjá tær verður viðgjørd + clicking_this_link: við at klikkja á hetta leinki + login_link: rita inn + proceed_to_login_html: Nú kanst tú fara víðari til %{login_link}. + redirect_to_app_html: Tú skuldi verið send/ur víðari til %{app_name} appina. Hendi tað ikki, so kanst tú royna %{clicking_this_link} ella fara manuelt aftur til appina. + registration_complete: Skráseting tín á %{domain} er nú avgreidd! + welcome_title: Vælkomin, %{name}! wrong_email_hint: Um hesin teldupoststaðurin ikki er rættur, so kanst tú broyta hann í kontustillingunum. delete_account: Strika kontu delete_account_html: Ynskir tú at strika kontuna, so kanst tú halda fram her. Tú verður spurd/ur um váttan. diff --git a/config/locales/fr-QC.yml b/config/locales/fr-QC.yml index 15e0fa60963229..41b71f569d1e31 100644 --- a/config/locales/fr-QC.yml +++ b/config/locales/fr-QC.yml @@ -1041,6 +1041,14 @@ fr-QC: hint_html: Juste une autre chose! Nous avons besoin de confirmer que vous êtes un humain (pour que nous puissions empêcher les spams!). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer". title: Vérification de sécurité confirmations: + awaiting_review: Votre adresse e-mail est confirmée ! L’équipe de %{domain} vérifie désormais votre inscription. Vous recevrez un e-mail si votre compte est approuvé ! + awaiting_review_title: Votre inscription est en cours de validation + clicking_this_link: cliquer sur ce lien + login_link: vous connecter + proceed_to_login_html: Vous pouvez désormais %{login_link}. + redirect_to_app_html: Vous auriez dû être redirigé vers l’application %{app_name}. Si cela ne s’est pas produit, essayez de %{clicking_this_link} ou de revenir manuellement à l’application. + registration_complete: Votre inscription sur %{domain} est désormais terminée ! + welcome_title: Bienvenue, %{name} ! wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte. delete_account: Supprimer le compte delete_account_html: Si vous désirez supprimer votre compte, vous pouvez cliquer ici. Il vous sera demandé de confirmer cette action. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1616f76726560f..79d8b92c2a3b6e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1041,6 +1041,14 @@ fr: hint_html: Encore une chose ! Nous avons besoin de confirmer que vous êtes un humain (c'est pour que nous puissions empêcher les spams !). Résolvez le CAPTCHA ci-dessous et cliquez sur "Continuer". title: Vérification de sécurité confirmations: + awaiting_review: Votre adresse e-mail est confirmée ! L’équipe de %{domain} vérifie désormais votre inscription. Vous recevrez un e-mail si votre compte est approuvé ! + awaiting_review_title: Votre inscription est en cours de validation + clicking_this_link: cliquer sur ce lien + login_link: vous connecter + proceed_to_login_html: Vous pouvez désormais %{login_link}. + redirect_to_app_html: Vous auriez dû être redirigé vers l’application %{app_name}. Si cela ne s’est pas produit, essayez de %{clicking_this_link} ou de revenir manuellement à l’application. + registration_complete: Votre inscription sur %{domain} est désormais terminée ! + welcome_title: Bienvenue, %{name} ! wrong_email_hint: Si cette adresse de courriel est incorrecte, vous pouvez la modifier dans vos paramètres de compte. delete_account: Supprimer le compte delete_account_html: Si vous désirez supprimer votre compte, vous pouvez cliquer ici. Il vous sera demandé de confirmer cette action. diff --git a/config/locales/fy.yml b/config/locales/fy.yml index a7ab89fc87c079..011899d15de056 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1041,6 +1041,14 @@ fy: hint_html: Noch ien ding! Jo moatte befêstigje dat jo in minske binne (dit is om de spam bûten de doar te hâlden!). Los de ûndersteande CAPTCHA op en klik op ‘Trochgean’. title: Befeiligingskontrôle confirmations: + awaiting_review: Jo e-mailadres is befêstige! De %{domain}-meiwurkers binne no dwaande mei it besjen fan jo registraasje. Jo ûntfange in e-mailberjocht as de jo account goedkarre! + awaiting_review_title: Jo registraasje wurdt beoardield + clicking_this_link: klik op dizze keppeling + login_link: oanmelde + proceed_to_login_html: Jo kinne no fierder gean nei %{login_link}. + redirect_to_app_html: Jo soene omlaad wêze moatte nei de %{app_name} app. As dat net bard is, probearje dan %{clicking_this_link} of kear hânmjittich werom nei de app. + registration_complete: Jo registraasje op %{domain} is no foltôge! + welcome_title: Wolkom, %{name}! wrong_email_hint: As it e-mailadres net korrekt is, kinne jo dat wizigje yn de accountynstellingen. delete_account: Account fuortsmite delete_account_html: Wannear’t jo jo account graach fuortsmite wolle, kinne jo dat hjir dwaan. Wy freegje jo dêr om in befêstiging. diff --git a/config/locales/he.yml b/config/locales/he.yml index 33f57155f2d130..d987931f1e5d0b 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1077,6 +1077,14 @@ he: hint_html: עוד דבר אחד, עלינו לאשרר שאת(ה) אנושיים (לצורך סינון ספאם). נא לפתור את הקאפצ'ה להלן וללחוץ "המשך". title: בדיקות אבטחה confirmations: + awaiting_review: כתובת הדואל שלך אושרה! צוות %{domain} עכשיו יבדוק את הרשמתך. תשלח אליך הודעת דואל אם הצוות יאשר את החשבון! + awaiting_review_title: הרשמתך עוברת בדיקה + clicking_this_link: לחיצה על קישור זה + login_link: כניסה + proceed_to_login_html: ניתן להמשיך עכשיו אל %{login_link}. + redirect_to_app_html: כאן אמורה היתה להיות הפניה אוטמטית ליישומון %{app_name}. אם זה לא קרה, ניתן לנסות שוב על ידי %{clicking_this_link} או חזרה ידנית אל היישומון. + registration_complete: הרשמתך לשרת %{domain} הושלמה כעת! + welcome_title: ברוך/ה הבא/ה, %{name}! wrong_email_hint: אם כתובת הדואל הזו איננה נכונה, ניתן לשנות אותה בעמוד ההגדרות. delete_account: מחיקת חשבון delete_account_html: אם ברצונך למחוק את החשבון, ניתן להמשיך כאן. תתבקש/י לספק אישור נוסף. diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 9d08786428349e..c01f38a9a9c758 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1041,13 +1041,13 @@ hu: hint_html: Még egy dolog! Meg kell győződnünk róla, hogy tényleg valós személy vagy (így távol tarthatjuk a spam-et). Oldd meg az alábbi CAPTCHA-t és kattints a "Folytatás"-ra. title: Biztonsági ellenőrzés confirmations: - awaiting_review: Az email cím megerősítésre került. %{domain} stáb elenleg áttrkinti a regisztrációt. Ha jóváhagyásra kerül a fiók, egy email kerül kiküldésre! - awaiting_review_title: A regisztráció áttekintés alatt áll. + awaiting_review: Az email címed megerősítésre került. %{domain} stábja jelenleg áttekinti a regisztrációdat. Ha jóváhagyásra került a fiókod, egy emailt küldenek majd! + awaiting_review_title: A regisztrációd áttekintés alatt áll clicking_this_link: kattintás erre a hivatkozásra login_link: bejelentkezés - proceed_to_login_html: 'Most továbbléphetünk: %{login_link}.' - redirect_to_app_html: Át kellett volna kerülni %{app_name} alkalmazáshoz. Ha ez nem történt meg, próbálkozzunk %{clicking_this_link} lehetőséggel vagy térjünk vissza manuálisan az alkalmazáshoz. - registration_complete: A regisztráció %{domain} domainen befejeződött! + proceed_to_login_html: 'Most továbbléphetsz: %{login_link}.' + redirect_to_app_html: Át kellett volna irányítsunk a %{app_name} alkalmazáshoz. Ha ez nem történt meg, próbálkozz a %{clicking_this_link} lehetőséggel vagy térj vissza manuálisan az alkalmazáshoz. + registration_complete: A regisztrációd %{domain} domainen befejeződött! welcome_title: Üdvözlet, %{name}! wrong_email_hint: Ha az emailcím nem helyes, a fiókbeállításokban megváltoztathatod. delete_account: Felhasználói fiók törlése diff --git a/config/locales/is.yml b/config/locales/is.yml index a229f0af027844..e0eb955699d612 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1045,9 +1045,13 @@ is: Leystu Turing skynprófið og smelltu á "Áfram". title: Öryggisathugun confirmations: + awaiting_review: Tölvupóstfangið þitt er staðfest. Umsjónarfólk %{domain} er núna að yfirfara skráninguna þína. Þú munt fá tölvupóst ef þau samþykkja skráninguna þína! + awaiting_review_title: Verið er að yfirfara skráninguna þína clicking_this_link: smella á þennan tengil login_link: skrá þig inn proceed_to_login_html: Þú getur núna farið í að %{login_link}. + redirect_to_app_html: Þér ætti að hafa verið endurbeint í %{app_name} forritið. Ef það gerðist ekki, skaltu prófa að %{clicking_this_link} eða fara aftur í forritið. + registration_complete: Skráning þín á %{domain} er núna tilbúin! welcome_title: Velkomin/n %{name}! wrong_email_hint: Ef það tölvupóstfang er ekki rétt geturðu breytt því í stillingum notandaaðgangsins. delete_account: Eyða notandaaðgangi diff --git a/config/locales/it.yml b/config/locales/it.yml index 6e41389bc586d6..e62ea620c96c0c 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1043,6 +1043,14 @@ it: hint_html: Solamente un'altra cosa! Dobbiamo confermare che tu sia un essere umano (così possiamo tenere fuori lo spam!). Risolvi il CAPTCHA sottostante e fai clic su "Continua". title: Controllo di sicurezza confirmations: + awaiting_review: Il tuo indirizzo e-mail è confermato! Lo staff di %{domain} sta esaminando la tua registrazione. Riceverai una e-mail se il tuo account sarà approvato! + awaiting_review_title: La tua registrazione è in corso di revisione + clicking_this_link: cliccando su questo link + login_link: accedi + proceed_to_login_html: Ora puoi procedere con il %{login_link}. + redirect_to_app_html: Avresti dovuto essere reindirizzato all'app %{app_name}. Se ciò non fosse avvenuto, prova %{clicking_this_link} o torna manualmente all'app. + registration_complete: La tua registrazione su %{domain} è ora completata! + welcome_title: Benvenutə, %{name}! wrong_email_hint: Se l'indirizzo e-mail non è corretto, puoi modificarlo nelle impostazioni dell'account. delete_account: Elimina account delete_account_html: Se desideri cancellare il tuo account, puoi farlo qui. Ti sarà chiesta conferma. diff --git a/config/locales/nl.yml b/config/locales/nl.yml index f107cbeca18c32..d8a1c8865687e0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1041,6 +1041,14 @@ nl: hint_html: Nog maar één ding! Je moet bevestigen dat je een mens bent (dit is om de spam buiten de deur te houden!). Los de onderstaande CAPTCHA op en klik op "Doorgaan". title: Veiligheidscontrole confirmations: + awaiting_review: Je e-mailadres is bevestigd! De %{domain}-medewerkers zijn nu bezig met het bekijken van jouw registratie. Je ontvangt een e-mailbericht als ze jouw account goedkeuren! + awaiting_review_title: Je registratie wordt beoordeeld + clicking_this_link: klik op deze koppeling + login_link: aanmelden + proceed_to_login_html: Je kunt nu verder gaan naar %{login_link}. + redirect_to_app_html: Je zou omgeleid moeten zijn naar de %{app_name} app. Als dat niet is gebeurd, probeer dan %{clicking_this_link} of keer handmatig terug naar de app. + registration_complete: Je registratie op %{domain} is nu voltooid! + welcome_title: Welkom, %{name}! wrong_email_hint: Als dat e-mailadres niet correct is, kun je het wijzigen in je accountinstellingen. delete_account: Account verwijderen delete_account_html: Wanneer je jouw account graag wilt verwijderen, kun je dat hier doen. We vragen jou daar om een bevestiging. diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index da79c7555ce4f8..4e64f9cfd93572 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1040,6 +1040,8 @@ pt-BR: hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isso é para que possamos evitar o spam!). Resolva o CAPTCHA abaixo e clique em "Continuar". title: Verificação de segurança confirmations: + registration_complete: Seu cadastro no %{domain} foi concluído! + welcome_title: Boas vindas, %{name}! wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta. delete_account: Excluir conta delete_account_html: Se você deseja excluir sua conta, você pode fazer isso aqui. Uma confirmação será solicitada. diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 62b497d70bf488..a40ac02f4ce0e6 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1041,6 +1041,14 @@ pt-PT: hint_html: Só mais uma coisa! Precisamos confirmar que você é um humano (isto para que possamos evitar spam!). Resolva o CAPTCHA abaixo e clique em "Continuar". title: Verificação de segurança confirmations: + awaiting_review: O seu endereço de correio eletrónico foi confirmado! A equipa %{domain} está agora a rever a sua inscrição. Receberá uma mensagem de correio eletrónico se aprovarem a sua conta! + awaiting_review_title: A sua inscrição está a ser revista + clicking_this_link: clicar nesta hiperligação + login_link: iniciar sessão + proceed_to_login_html: Pode agora prosseguir para %{login_link}. + redirect_to_app_html: Devia ter sido reencaminhado para a aplicação %{app_name}. Se isso não aconteceu, tente %{clicking_this_link} ou volte manualmente para a aplicação. + registration_complete: O seu registo sem %{domain} está agora concluído! + welcome_title: Bem-vindo(a), %{name}! wrong_email_hint: Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta. delete_account: Eliminar conta delete_account_html: Se deseja eliminar a sua conta, pode continuar aqui. Uma confirmação será solicitada. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index f9e526c4e8edf4..945b190435b36b 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -556,6 +556,7 @@ ru: total_reported: Жалобы на них total_storage: Медиафайлы totals_time_period_hint_html: Итоги, показанные ниже, включают данные за всё время. + unknown_instance: На данный момент нет записей об этом домене на этом сервере. invites: deactivate_all: Отключить все filter: @@ -1076,6 +1077,12 @@ ru: hint_html: Еще одна вещь! Нам нужно подтвердить, что вы человек (так что мы можем держать спам!). Решите капчу ниже и нажмите кнопку «Продолжить». title: Проверка безопасности confirmations: + awaiting_review: Ваш адрес электронной почты подтвержден! Сотрудники %{domain} проверяют вашу регистрацию. Вы получите письмо, если они подтвердят вашу учетную запись! + awaiting_review_title: Ваша регистрация проверяется + login_link: войти + proceed_to_login_html: Теперь вы можете перейти к %{login_link}. + registration_complete: Ваша регистрация на %{domain} завершена! + welcome_title: Добро пожаловать, %{name}! wrong_email_hint: Если этот адрес электронной почты неверен, вы можете изменить его в настройках аккаунта. delete_account: Удалить учётную запись delete_account_html: Удалить свою учётную запись можно в два счёта здесь, но прежде у вас будет спрошено подтверждение. @@ -1137,6 +1144,7 @@ ru: functional: Ваша учётная запись в полном порядке. pending: Ваша заявка ожидает одобрения администраторами, это может занять немного времени. Вы получите письмо, как только заявку одобрят. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. + self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи. view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи too_fast: Форма отправлена слишком быстро, попробуйте еще раз. use_security_key: Использовать ключ безопасности @@ -1622,6 +1630,8 @@ ru: over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день over_total_limit: Вы превысили лимит на %{limit} запланированных постов too_soon: Запланированная дата должна быть в будущем + self_destruct: + title: Этот сервер закрывается sessions: activity: Последняя активность browser: Браузер diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 1cccb301d19dcf..1d84f8a6af0b3d 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1043,8 +1043,9 @@ sv: confirmations: awaiting_review_title: Din registrering är under granskning clicking_this_link: klicka på denna länk + login_link: logga in registration_complete: Din registrering på %{domain} är nu slutförd! - welcome_title: Välkommen, %{name}! + welcome_title: Välkommen %{name}! wrong_email_hint: Om e-postadressen inte är rätt, kan du ändra den i kontoinställningarna. delete_account: Radera konto delete_account_html: Om du vill radera ditt konto kan du fortsätta här. Du kommer att bli ombedd att bekräfta. diff --git a/config/locales/th.yml b/config/locales/th.yml index d8ab63730bec4a..33d8a898ede6a6 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1023,6 +1023,14 @@ th: hint_html: อีกเพียงหนึ่งสิ่งเพิ่มเติม! เราจำเป็นต้องยืนยันว่าคุณเป็นมนุษย์ (นี่ก็เพื่อให้เราสามารถกันสแปมออกไป!) แก้ CAPTCHA ด้านล่างและคลิก "ดำเนินการต่อ" title: การตรวจสอบความปลอดภัย confirmations: + awaiting_review: ยืนยันที่อยู่อีเมลของคุณแล้ว! ตอนนี้พนักงานของ %{domain} กำลังตรวจทานการลงทะเบียนของคุณ คุณจะได้รับอีเมลหากพนักงานอนุมัติบัญชีของคุณ! + awaiting_review_title: กำลังตรวจทานการลงทะเบียนของคุณ + clicking_this_link: คลิกลิงก์นี้ + login_link: เข้าสู่ระบบ + proceed_to_login_html: ตอนนี้คุณสามารถดำเนินการต่อเพื่อ %{login_link} + redirect_to_app_html: คุณควรได้รับการเปลี่ยนเส้นทางไปยังแอป %{app_name} หากนั่นไม่เกิดขึ้น ลอง %{clicking_this_link} หรือกลับไปยังแอปด้วยตนเอง + registration_complete: ตอนนี้การลงทะเบียนของคุณใน %{domain} เสร็จสมบูรณ์แล้ว! + welcome_title: ยินดีต้อนรับ %{name}! wrong_email_hint: หากที่อยู่อีเมลนั้นไม่ถูกต้อง คุณสามารถเปลี่ยนที่อยู่อีเมลได้ในการตั้งค่าบัญชี delete_account: ลบบัญชี delete_account_html: หากคุณต้องการลบบัญชีของคุณ คุณสามารถ ดำเนินการต่อที่นี่ คุณจะได้รับการถามเพื่อการยืนยัน diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 86d153528b9541..c9adfd91322182 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1041,6 +1041,14 @@ tr: hint_html: Sadece bir şey daha! Sizin bir insan olduğunuzu doğrulamamız gerekiyor (bu, spam'i dışarıda tutabilmemiz içindir!). Aşağıdaki CAPTCHA'yı çözün ve "Devam Et" düğmesini tıklayın. title: Güvenlik denetimi confirmations: + awaiting_review: E-posta adresiniz doğrulandı! %{domain} çalışanları şimdi kaydınızı inceliyorlar. Hesabınızı onayladıklarında bir e-posta alacaksınız! + awaiting_review_title: Kaydınız inceleniyor + clicking_this_link: bu bağlantıyı tıklamayı + login_link: oturum aç + proceed_to_login_html: Şimdi %{login_link} devam edebilirsiniz. + redirect_to_app_html: "%{app_name} uygulamasına yönlendirileceksiniz. Eğer yönlendirme olmazsa, %{clicking_this_link} veya uygulamaya geri dönmeyi deneyin." + registration_complete: "%{domain} sunucusunda kaydınız şimdi tamamlandı!" + welcome_title: Hoşgeldin %{name}! wrong_email_hint: Eğer bu e-posta adresi doğru değilse, hesap ayarlarında değiştirebilirsiniz. delete_account: Hesabı sil delete_account_html: Hesabını silmek istersen, buradan devam edebilirsin. Onay istenir. diff --git a/config/locales/uk.yml b/config/locales/uk.yml index afa85ae67bb54a..8069c76c6d95c4 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1077,6 +1077,14 @@ uk: hint_html: Ще одне! Ми повинні пересвідчитись, що ви людина (щоб ми могли уникнути спаму!). Розв'яжіть CAPTCHA внизу і натисніть кнопку "Продовжити". title: Перевірка безпеки confirmations: + awaiting_review: Ваша електронна адреса підтверджена! Співробітники %{domain} тепер переглядають вашу реєстрацію. Ви отримаєте електронного листа, якщо вони затвердять ваш обліковий запис! + awaiting_review_title: Ваша реєстрація розглядається + clicking_this_link: натисніть це посилання + login_link: увійти + proceed_to_login_html: Тепер ви можете перейти до %{login_link}. + redirect_to_app_html: Ви мали бути перенаправлені до програми %{app_name}. Якщо цього не сталося, спробуйте %{clicking_this_link} або вручну поверніться до програми. + registration_complete: Ваша реєстрація на %{domain} завершена! + welcome_title: Ласкаво просимо, %{name}! wrong_email_hint: Якщо ця адреса електронної пошти неправильна, можна змінити її в налаштуваннях облікового запису. delete_account: Видалити обліковий запис delete_account_html: Якщо ви хочете видалити свій обліковий запис, ви можете перейти сюди. Вас попросять підтвердити дію. From 37929b9707a8421a43da15d5629811688b29b4fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:56:45 +0200 Subject: [PATCH 05/13] Update libretranslate/libretranslate Docker tag to v1.4.0 (#27504) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 0369521963f16c..f1945f8a648083 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -70,7 +70,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.3.12 + image: libretranslate/libretranslate:v1.4.0 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local From 1f5187e2e28828c02c6c1ee1c00d6629836f8969 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Oct 2023 05:57:16 -0400 Subject: [PATCH 06/13] Misc spec/refactor to user mailer and user mailer spec (#27486) --- .../admin/disputes/appeals_controller.rb | 2 +- app/mailers/user_mailer.rb | 62 +++++++++---------- config/i18n-tasks.yml | 1 + spec/mailers/user_mailer_spec.rb | 51 ++++++++++----- 4 files changed, 65 insertions(+), 51 deletions(-) diff --git a/app/controllers/admin/disputes/appeals_controller.rb b/app/controllers/admin/disputes/appeals_controller.rb index 32e5e2f6fd82ed..5e342409b021cb 100644 --- a/app/controllers/admin/disputes/appeals_controller.rb +++ b/app/controllers/admin/disputes/appeals_controller.rb @@ -20,7 +20,7 @@ def reject authorize @appeal, :approve? log_action :reject, @appeal @appeal.reject!(current_account) - UserMailer.appeal_rejected(@appeal.account.user, @appeal) + UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later redirect_to disputes_strike_path(@appeal.strike) end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 2889d13b536526..2af2a3a41d2cb3 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -8,13 +8,15 @@ class UserMailer < Devise::Mailer helper :instance helper :statuses helper :formatting + helper :routing - helper RoutingHelper + before_action :set_instance + + default to: -> { @resource.email } def confirmation_instructions(user, token, *, **) @resource = user @token = token - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? @@ -28,185 +30,177 @@ def confirmation_instructions(user, token, *, **) def reset_password_instructions(user, token, *, **) @resource = user @token = token - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject') + mail subject: default_devise_subject end end def password_change(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') + mail subject: default_devise_subject end end def email_changed(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject') + mail subject: default_devise_subject end end def two_factor_enabled(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject') + mail subject: default_devise_subject end end def two_factor_disabled(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject') + mail subject: default_devise_subject end end def two_factor_recovery_codes_changed(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject') + mail subject: default_devise_subject end end def webauthn_enabled(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject') + mail subject: default_devise_subject end end def webauthn_disabled(user, *, **) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject') + mail subject: default_devise_subject end end def webauthn_credential_added(user, webauthn_credential) @resource = user - @instance = Rails.configuration.x.local_domain @webauthn_credential = webauthn_credential return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject') + mail subject: I18n.t('devise.mailer.webauthn_credential.added.subject') end end def webauthn_credential_deleted(user, webauthn_credential) @resource = user - @instance = Rails.configuration.x.local_domain @webauthn_credential = webauthn_credential return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject') + mail subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject') end end def welcome(user) @resource = user - @instance = Rails.configuration.x.local_domain return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject') + mail subject: default_i18n_subject end end def backup_ready(user, backup) @resource = user - @instance = Rails.configuration.x.local_domain @backup = backup return unless @resource.active_for_authentication? I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject') + mail subject: default_i18n_subject end end def warning(user, warning) @resource = user @warning = warning - @instance = Rails.configuration.x.local_domain @statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account]) I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}") + mail subject: I18n.t("user_mailer.warning.subject.#{@warning.action}", acct: "@#{user.account.local_username_and_domain}") end end def appeal_approved(user, appeal) @resource = user - @instance = Rails.configuration.x.local_domain @appeal = appeal I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('user_mailer.appeal_approved.subject', date: l(@appeal.created_at)) + mail subject: default_i18n_subject(date: l(@appeal.created_at)) end end def appeal_rejected(user, appeal) @resource = user - @instance = Rails.configuration.x.local_domain @appeal = appeal I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('user_mailer.appeal_rejected.subject', date: l(@appeal.created_at)) + mail subject: default_i18n_subject(date: l(@appeal.created_at)) end end def suspicious_sign_in(user, remote_ip, user_agent, timestamp) @resource = user - @instance = Rails.configuration.x.local_domain @remote_ip = remote_ip @user_agent = user_agent @detection = Browser.new(user_agent) @timestamp = timestamp.to_time.utc I18n.with_locale(locale) do - mail to: @resource.email, subject: I18n.t('user_mailer.suspicious_sign_in.subject') + mail subject: default_i18n_subject end end private + def default_devise_subject + I18n.t(:subject, scope: ['devise.mailer', action_name]) + end + + def set_instance + @instance = Rails.configuration.x.local_domain + end + def locale @resource.locale.presence || I18n.default_locale end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 2d4487ce56ce05..e2ea4153c93c68 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -64,6 +64,7 @@ ignore_unused: - 'statuses.attached.*' - 'move_handler.carry_{mutes,blocks}_over_text' - 'admin_mailer.*.subject' + - 'user_mailer.*.subject' - 'notification_mailer.*' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html' diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 5affa66e078eab..c661f5bbda552d 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -5,7 +5,7 @@ describe UserMailer do let(:receiver) { Fabricate(:user) } - describe 'confirmation_instructions' do + describe '#confirmation_instructions' do let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } it 'renders confirmation instructions' do @@ -20,7 +20,7 @@ instance: Rails.configuration.x.local_domain end - describe 'reconfirmation_instructions' do + describe '#reconfirmation_instructions' do let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } it 'renders reconfirmation instructions' do @@ -34,7 +34,7 @@ end end - describe 'reset_password_instructions' do + describe '#reset_password_instructions' do let(:mail) { described_class.reset_password_instructions(receiver, 'spec') } it 'renders reset password instructions' do @@ -47,7 +47,7 @@ 'devise.mailer.reset_password_instructions.subject' end - describe 'password_change' do + describe '#password_change' do let(:mail) { described_class.password_change(receiver) } it 'renders password change notification' do @@ -59,7 +59,7 @@ 'devise.mailer.password_change.subject' end - describe 'email_changed' do + describe '#email_changed' do let(:mail) { described_class.email_changed(receiver) } it 'renders email change notification' do @@ -71,7 +71,7 @@ 'devise.mailer.email_changed.subject' end - describe 'warning' do + describe '#warning' do let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') } let(:mail) { described_class.warning(receiver, strike) } @@ -82,7 +82,7 @@ end end - describe 'webauthn_credential_deleted' do + describe '#webauthn_credential_deleted' do let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) } let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) } @@ -95,7 +95,7 @@ 'devise.mailer.webauthn_credential.deleted.subject' end - describe 'suspicious_sign_in' do + describe '#suspicious_sign_in' do let(:ip) { '192.168.0.1' } let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } let(:timestamp) { Time.now.utc } @@ -110,7 +110,7 @@ 'user_mailer.suspicious_sign_in.subject' end - describe 'appeal_approved' do + describe '#appeal_approved' do let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) } let(:mail) { described_class.appeal_approved(receiver, appeal) } @@ -120,7 +120,7 @@ end end - describe 'appeal_rejected' do + describe '#appeal_rejected' do let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) } let(:mail) { described_class.appeal_rejected(receiver, appeal) } @@ -130,7 +130,7 @@ end end - describe 'two_factor_enabled' do + describe '#two_factor_enabled' do let(:mail) { described_class.two_factor_enabled(receiver) } it 'renders two_factor_enabled mail' do @@ -139,7 +139,7 @@ end end - describe 'two_factor_disabled' do + describe '#two_factor_disabled' do let(:mail) { described_class.two_factor_disabled(receiver) } it 'renders two_factor_disabled mail' do @@ -148,7 +148,7 @@ end end - describe 'webauthn_enabled' do + describe '#webauthn_enabled' do let(:mail) { described_class.webauthn_enabled(receiver) } it 'renders webauthn_enabled mail' do @@ -157,7 +157,7 @@ end end - describe 'webauthn_disabled' do + describe '#webauthn_disabled' do let(:mail) { described_class.webauthn_disabled(receiver) } it 'renders webauthn_disabled mail' do @@ -166,7 +166,7 @@ end end - describe 'two_factor_recovery_codes_changed' do + describe '#two_factor_recovery_codes_changed' do let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) } it 'renders two_factor_recovery_codes_changed mail' do @@ -175,7 +175,7 @@ end end - describe 'webauthn_credential_added' do + describe '#webauthn_credential_added' do let(:credential) { Fabricate.build(:webauthn_credential) } let(:mail) { described_class.webauthn_credential_added(receiver, credential) } @@ -184,4 +184,23 @@ expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation') end end + + describe '#welcome' do + let(:mail) { described_class.welcome(receiver) } + + it 'renders welcome mail' do + expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject') + expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation') + end + end + + describe '#backup_ready' do + let(:backup) { Fabricate(:backup) } + let(:mail) { described_class.backup_ready(receiver, backup) } + + it 'renders backup_ready mail' do + expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject') + expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation') + end + end end From 13d310e64df9aee293d434eb68d541b9002ec2e9 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 27 Oct 2023 15:21:07 +0200 Subject: [PATCH 07/13] Simplify column headers (#27557) --- .../components/__tests__/button-test.jsx | 3 +- .../components/column_back_button.tsx | 25 -------- .../mastodon/components/column_header.jsx | 55 +++++++++------- .../mastodon/features/blocks/index.jsx | 4 +- .../mastodon/features/domain_blocks/index.jsx | 5 +- .../features/follow_requests/index.jsx | 4 +- .../mastodon/features/mutes/index.jsx | 4 +- .../features/pinned_statuses/index.jsx | 4 +- .../ui/components/__tests__/column-test.jsx | 8 ++- .../features/ui/components/column.jsx | 9 +-- .../features/ui/components/column_header.jsx | 41 ------------ app/javascript/mastodon/test_helpers.tsx | 62 +++++++++++++++++++ .../styles/mastodon/components.scss | 14 ----- 13 files changed, 113 insertions(+), 125 deletions(-) delete mode 100644 app/javascript/mastodon/features/ui/components/column_header.jsx create mode 100644 app/javascript/mastodon/test_helpers.tsx diff --git a/app/javascript/mastodon/components/__tests__/button-test.jsx b/app/javascript/mastodon/components/__tests__/button-test.jsx index ad7a0c49cab724..f38ff6a7ddf8c3 100644 --- a/app/javascript/mastodon/components/__tests__/button-test.jsx +++ b/app/javascript/mastodon/components/__tests__/button-test.jsx @@ -1,6 +1,7 @@ -import { render, fireEvent, screen } from '@testing-library/react'; import renderer from 'react-test-renderer'; +import { render, fireEvent, screen } from 'mastodon/test_helpers'; + import { Button } from '../button'; describe('); + +}; + +BackButton.propTypes = { + pinned: PropTypes.bool, + show: PropTypes.bool, +}; + class ColumnHeader extends PureComponent { static contextTypes = { @@ -72,16 +102,6 @@ class ColumnHeader extends PureComponent { this.props.onMove(1); }; - handleBackClick = () => { - const { history } = this.props; - - if (history.location?.state?.fromMastodon) { - history.goBack(); - } else { - history.push('/'); - } - }; - handleTransitionEnd = () => { this.setState({ animating: false }); }; @@ -95,7 +115,7 @@ class ColumnHeader extends PureComponent { }; render () { - const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; + const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -138,14 +158,7 @@ class ColumnHeader extends PureComponent { pinButton = ; } - if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { - backButton = ( - - ); - } + backButton = ; const collapsedContent = [ extraContent, diff --git a/app/javascript/mastodon/features/blocks/index.jsx b/app/javascript/mastodon/features/blocks/index.jsx index 21b7a263f18f90..615e4c8be22fef 100644 --- a/app/javascript/mastodon/features/blocks/index.jsx +++ b/app/javascript/mastodon/features/blocks/index.jsx @@ -10,7 +10,6 @@ import { ReactComponent as BlockIcon } from '@material-symbols/svg-600/outlined/ import { debounce } from 'lodash'; import { fetchBlocks, expandBlocks } from '../../actions/blocks'; -import { ColumnBackButtonSlim } from '../../components/column_back_button'; import { LoadingIndicator } from '../../components/loading_indicator'; import ScrollableList from '../../components/scrollable_list'; import AccountContainer from '../../containers/account_container'; @@ -60,8 +59,7 @@ class Blocks extends ImmutablePureComponent { const emptyMessage = ; return ( - - + ; return ( - - - + - + ; return ( - - + - + ; + describe('', () => { describe(' click handler', () => { it('runs the scroll animation if the column contains scrollable content', () => { const scrollToMock = jest.fn(); const { container } = render( - +
, ); @@ -17,7 +19,7 @@ describe('', () => { }); it('does not try to scroll if there is no scrollable content', () => { - render(); + render(); fireEvent.click(screen.getByText('notifications')); }); }); diff --git a/app/javascript/mastodon/features/ui/components/column.jsx b/app/javascript/mastodon/features/ui/components/column.jsx index d667f42d995e0c..b6c09b62cd26d3 100644 --- a/app/javascript/mastodon/features/ui/components/column.jsx +++ b/app/javascript/mastodon/features/ui/components/column.jsx @@ -3,15 +3,15 @@ import { PureComponent } from 'react'; import { debounce } from 'lodash'; +import ColumnHeader from '../../../components/column_header'; import { isMobile } from '../../../is_mobile'; import { scrollTop } from '../../../scroll'; -import ColumnHeader from './column_header'; - export default class Column extends PureComponent { static propTypes = { heading: PropTypes.string, + alwaysShowBackButton: PropTypes.bool, icon: PropTypes.string, iconComponent: PropTypes.func, children: PropTypes.node, @@ -51,13 +51,14 @@ export default class Column extends PureComponent { }; render () { - const { heading, icon, iconComponent, children, active, hideHeadingOnMobile } = this.props; + const { heading, icon, iconComponent, children, active, hideHeadingOnMobile, alwaysShowBackButton } = this.props; const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); const columnHeaderId = showHeading && heading.replace(/ /g, '-'); + const header = showHeading && ( - + ); return (
{ - this.props.onClick(); - }; - - render () { - const { icon, iconComponent, type, active, columnHeaderId } = this.props; - let iconElement = ''; - - if (icon) { - iconElement = ; - } - - return ( -

- -

- ); - } - -} diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx new file mode 100644 index 00000000000000..689589556956b4 --- /dev/null +++ b/app/javascript/mastodon/test_helpers.tsx @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types'; +import type { PropsWithChildren } from 'react'; +import { Component } from 'react'; + +import { IntlProvider } from 'react-intl'; + +import { MemoryRouter } from 'react-router'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import { render as rtlRender } from '@testing-library/react'; + +class FakeIdentityWrapper extends Component< + PropsWithChildren<{ signedIn: boolean }> +> { + static childContextTypes = { + identity: PropTypes.shape({ + signedIn: PropTypes.bool.isRequired, + accountId: PropTypes.string, + disabledAccountId: PropTypes.string, + accessToken: PropTypes.string, + }).isRequired, + }; + + getChildContext() { + return { + identity: { + signedIn: this.props.signedIn, + accountId: '123', + accessToken: 'test-access-token', + }, + }; + } + + render() { + return this.props.children; + } +} + +function render( + ui: React.ReactElement, + { locale = 'en', signedIn = true, ...renderOptions } = {}, +) { + const Wrapper = (props: { children: React.ReactElement }) => { + return ( + + + + {props.children} + + + + ); + }; + return rtlRender(ui, { wrapper: Wrapper, ...renderOptions }); +} + +// re-export everything +// eslint-disable-next-line import/no-extraneous-dependencies +export * from '@testing-library/react'; + +// override render method +export { render }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b8e6af0aaaf231..1e341680e09367 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3137,20 +3137,6 @@ $ui-header-height: 55px; margin-inline-end: 5px; } -.column-back-button--slim { - position: relative; -} - -.column-back-button--slim-button { - cursor: pointer; - flex: 0 0 auto; - font-size: 16px; - padding: 15px; - position: absolute; - inset-inline-end: 0; - top: -50px; -} - .react-toggle { display: inline-block; position: relative; From 15ef654e9a9f81e6d2fc39b2f60397df5c9020ce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:43:00 +0200 Subject: [PATCH 08/13] Update dependency pundit to v2.3.1 (#27585) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 33e355a06b4c7d..5021800eba4429 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -532,7 +532,7 @@ GEM public_suffix (5.0.3) puma (6.4.0) nio4r (~> 2.0) - pundit (2.3.0) + pundit (2.3.1) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.7.1) From 08bdd5751e706080077041f89cfdc031434fa0c3 Mon Sep 17 00:00:00 2001 From: SouthFox <58982705+SouthFox-D@users.noreply.github.com> Date: Fri, 27 Oct 2023 22:03:21 +0800 Subject: [PATCH 09/13] Fix account click on detailed status (#27587) --- .../mastodon/features/status/components/detailed_status.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index baddc67310e536..d8d9559127d251 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -58,7 +58,7 @@ class DetailedStatus extends ImmutablePureComponent { handleAccountClick = (e) => { if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) { e.preventDefault(); - this.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } e.stopPropagation(); From 93e4cdc31b69bb1f248e3e6b6811d93fe2522d4b Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 27 Oct 2023 16:04:51 +0200 Subject: [PATCH 10/13] Fix hashtag matching pattern matching some URLs (#27584) --- app/models/tag.rb | 2 +- spec/models/tag_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index 413f6f5004766e..8fab98fb5c0107 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -35,7 +35,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" - HASHTAG_RE = %r{(? Date: Fri, 27 Oct 2023 16:55:00 +0200 Subject: [PATCH 11/13] Have `Follow` activities bypass availability (#27586) Co-authored-by: Claire --- app/services/follow_service.rb | 2 +- app/workers/activitypub/delivery_worker.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index feea40e3c0a945..1aa0241fe62325 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -71,7 +71,7 @@ def request_follow! if @target_account.local? LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request') elsif @target_account.activitypub? - ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url) + ActivityPub::DeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url, { 'bypass_availability' => true }) end follow_request diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 7c1c14766b70a5..376c237a98493a 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -23,9 +23,10 @@ class ActivityPub::DeliveryWorker HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze def perform(json, source_account_id, inbox_url, options = {}) - return unless DeliveryFailureTracker.available?(inbox_url) - @options = options.with_indifferent_access + + return unless @options[:bypass_availability] || DeliveryFailureTracker.available?(inbox_url) + @json = json @source_account = Account.find(source_account_id) @inbox_url = inbox_url From 2e6bf60f1549e5c1f1cfea2d614f978bea17b8a2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Oct 2023 11:33:52 -0400 Subject: [PATCH 12/13] Use `deliveries.size` in mailer-related examples in controller specs (#27589) --- .../admin/disputes/appeals_controller_spec.rb | 12 ++++++------ spec/controllers/auth/sessions_controller_spec.rb | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb index 3f4175a28177c9..4581d00d6f7def 100644 --- a/spec/controllers/admin/disputes/appeals_controller_spec.rb +++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb @@ -33,8 +33,6 @@ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } before do - allow(UserMailer).to receive(:appeal_approved) - .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil)) post :approve, params: { id: appeal.id } end @@ -47,7 +45,9 @@ end it 'notifies target account about approved appeal' do - expect(UserMailer).to have_received(:appeal_approved).with(target_account.user, appeal) + expect(UserMailer.deliveries.size).to eq(1) + expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email) + expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))) end end @@ -55,8 +55,6 @@ let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } before do - allow(UserMailer).to receive(:appeal_rejected) - .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later: nil)) post :reject, params: { id: appeal.id } end @@ -65,7 +63,9 @@ end it 'notifies target account about rejected appeal' do - expect(UserMailer).to have_received(:appeal_rejected).with(target_account.user, appeal) + expect(UserMailer.deliveries.size).to eq(1) + expect(UserMailer.deliveries.first.to.first).to eq(target_account.user.email) + expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))) end end end diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index c727a763339af9..3ff9b150079403 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -127,8 +127,6 @@ before do allow_any_instance_of(ActionDispatch::Request).to receive(:remote_ip).and_return(current_ip) - allow(UserMailer).to receive(:suspicious_sign_in) - .and_return(instance_double(ActionMailer::MessageDelivery, deliver_later!: nil)) user.update(current_sign_in_at: 1.month.ago) post :create, params: { user: { email: user.email, password: user.password } } end @@ -142,7 +140,9 @@ end it 'sends a suspicious sign-in mail' do - expect(UserMailer).to have_received(:suspicious_sign_in).with(user, current_ip, anything, anything) + expect(UserMailer.deliveries.size).to eq(1) + expect(UserMailer.deliveries.first.to.first).to eq(user.email) + expect(UserMailer.deliveries.first.subject).to eq(I18n.t('user_mailer.suspicious_sign_in.subject')) end end From eae5c7334ae61c463edd2e3cd03115b897f6e92b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Oct 2023 12:20:40 -0400 Subject: [PATCH 13/13] Extract class from CSP configuration/initialization (#26905) --- app/lib/content_security_policy.rb | 59 ++++++++ .../initializers/content_security_policy.rb | 21 +-- spec/lib/content_security_policy_spec.rb | 129 ++++++++++++++++++ 3 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 app/lib/content_security_policy.rb create mode 100644 spec/lib/content_security_policy_spec.rb diff --git a/app/lib/content_security_policy.rb b/app/lib/content_security_policy.rb new file mode 100644 index 00000000000000..e8fcf76a6564d7 --- /dev/null +++ b/app/lib/content_security_policy.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class ContentSecurityPolicy + def base_host + Rails.configuration.x.web_domain + end + + def assets_host + url_from_configured_asset_host || url_from_base_host + end + + def media_host + cdn_host_value || assets_host + end + + private + + def url_from_configured_asset_host + Rails.configuration.action_controller.asset_host + end + + def cdn_host_value + s3_alias_host || s3_cloudfront_host || azure_alias_host || s3_hostname_host + end + + def url_from_base_host + host_to_url(base_host) + end + + def host_to_url(host_string) + uri_from_configuration_and_string(host_string) if host_string.present? + end + + def s3_alias_host + host_to_url ENV.fetch('S3_ALIAS_HOST', nil) + end + + def s3_cloudfront_host + host_to_url ENV.fetch('S3_CLOUDFRONT_HOST', nil) + end + + def azure_alias_host + host_to_url ENV.fetch('AZURE_ALIAS_HOST', nil) + end + + def s3_hostname_host + host_to_url ENV.fetch('S3_HOSTNAME', nil) + end + + def uri_from_configuration_and_string(host_string) + Addressable::URI.parse("#{host_protocol}://#{host_string}").tap do |uri| + uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/') + end.to_s + end + + def host_protocol + Rails.configuration.x.use_https ? 'https' : 'http' + end +end diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index e5e672f2c774c1..8f8ea580288f54 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -6,24 +6,11 @@ # See the Securing Rails Applications Guide for more information: # https://guides.rubyonrails.org/security.html#content-security-policy-header -def host_to_url(str) - return if str.blank? +require_relative '../../app/lib/content_security_policy' - uri = Addressable::URI.parse("http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}") - uri.path += '/' unless uri.path.blank? || uri.path.end_with?('/') - uri.to_s -end - -base_host = Rails.configuration.x.web_domain - -assets_host = Rails.configuration.action_controller.asset_host -assets_host ||= host_to_url(base_host) - -media_host = host_to_url(ENV['S3_ALIAS_HOST']) -media_host ||= host_to_url(ENV['S3_CLOUDFRONT_HOST']) -media_host ||= host_to_url(ENV['AZURE_ALIAS_HOST']) -media_host ||= host_to_url(ENV['S3_HOSTNAME']) if ENV['S3_ENABLED'] == 'true' -media_host ||= assets_host +policy = ContentSecurityPolicy.new +assets_host = policy.assets_host +media_host = policy.media_host def sso_host return unless ENV['ONE_CLICK_SSO_LOGIN'] == 'true' diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb new file mode 100644 index 00000000000000..2e92f815acc897 --- /dev/null +++ b/spec/lib/content_security_policy_spec.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ContentSecurityPolicy do + subject { described_class.new } + + around do |example| + original_asset_host = Rails.configuration.action_controller.asset_host + original_web_domain = Rails.configuration.x.web_domain + original_use_https = Rails.configuration.x.use_https + example.run + Rails.configuration.action_controller.asset_host = original_asset_host + Rails.configuration.x.web_domain = original_web_domain + Rails.configuration.x.use_https = original_use_https + end + + describe '#base_host' do + before { Rails.configuration.x.web_domain = 'host.example' } + + it 'returns the configured value for the web domain' do + expect(subject.base_host).to eq 'host.example' + end + end + + describe '#assets_host' do + context 'when asset_host is not configured' do + before { Rails.configuration.action_controller.asset_host = nil } + + context 'with a configured web domain' do + before { Rails.configuration.x.web_domain = 'host.example' } + + context 'when use_https is enabled' do + before { Rails.configuration.x.use_https = true } + + it 'returns value from base host with https protocol' do + expect(subject.assets_host).to eq 'https://host.example' + end + end + + context 'when use_https is disabled' do + before { Rails.configuration.x.use_https = false } + + it 'returns value from base host with http protocol' do + expect(subject.assets_host).to eq 'http://host.example' + end + end + end + end + + context 'when asset_host is configured' do + before do + Rails.configuration.action_controller.asset_host = 'https://assets.host.example' + end + + it 'returns full value from configured host' do + expect(subject.assets_host).to eq 'https://assets.host.example' + end + end + end + + describe '#media_host' do + context 'when there is no configured CDN' do + it 'defaults to using the assets_host value' do + expect(subject.media_host).to eq(subject.assets_host) + end + end + + context 'when an S3 alias host is configured' do + around do |example| + ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example' do + example.run + end + end + + it 'uses the s3 alias host value' do + expect(subject.media_host).to eq 'https://asset-host.s3-alias.example' + end + end + + context 'when an S3 alias host with a trailing path is configured' do + around do |example| + ClimateControl.modify S3_ALIAS_HOST: 'asset-host.s3-alias.example/pathname' do + example.run + end + end + + it 'uses the s3 alias host value and preserves the path' do + expect(subject.media_host).to eq 'https://asset-host.s3-alias.example/pathname/' + end + end + + context 'when an S3 cloudfront host is configured' do + around do |example| + ClimateControl.modify S3_CLOUDFRONT_HOST: 'asset-host.s3-cloudfront.example' do + example.run + end + end + + it 'uses the s3 cloudfront host value' do + expect(subject.media_host).to eq 'https://asset-host.s3-cloudfront.example' + end + end + + context 'when an azure alias host is configured' do + around do |example| + ClimateControl.modify AZURE_ALIAS_HOST: 'asset-host.azure-alias.example' do + example.run + end + end + + it 'uses the azure alias host value' do + expect(subject.media_host).to eq 'https://asset-host.azure-alias.example' + end + end + + context 'when s3_enabled is configured' do + around do |example| + ClimateControl.modify S3_ENABLED: 'true', S3_HOSTNAME: 'asset-host.s3.example' do + example.run + end + end + + it 'uses the s3 hostname host value' do + expect(subject.media_host).to eq 'https://asset-host.s3.example' + end + end + end +end