Merge commit '5b457961fc1189a71599dc6c06b3f159b195a455' into glitch-soc/merge-upstream
Conflicts: - `config/initializers/content_security_policy.rb`: Upstream fixed an issue that was not present in glitch-soc. Kept our version.
This commit is contained in:
		
						commit
						42f36aa55a
					
				|  | @ -297,7 +297,7 @@ export default class Dropdown extends PureComponent { | |||
|       onKeyPress: this.handleKeyPress, | ||||
|     }) : ( | ||||
|       <IconButton | ||||
|         icon={icon} | ||||
|         icon={!open ? icon : 'close'} | ||||
|         title={title} | ||||
|         active={open} | ||||
|         disabled={disabled} | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
| import { Avatar } from '../../../components/avatar'; | ||||
| import { IconButton } from '../../../components/icon_button'; | ||||
| 
 | ||||
| import ActionBar from './action_bar'; | ||||
| 
 | ||||
|  | @ -21,23 +20,27 @@ export default class NavigationBar extends ImmutablePureComponent { | |||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const username = this.props.account.get('acct') | ||||
|     return ( | ||||
|       <div className='navigation-bar'> | ||||
|         <Link to={`/@${this.props.account.get('acct')}`}> | ||||
|           <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span> | ||||
|         <Link to={`/@${username}`}> | ||||
|           <span style={{ display: 'none' }}>{username}</span> | ||||
|           <Avatar account={this.props.account} size={46} /> | ||||
|         </Link> | ||||
| 
 | ||||
|         <div className='navigation-bar__profile'> | ||||
|           <Link to={`/@${this.props.account.get('acct')}`}> | ||||
|             <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong> | ||||
|           </Link> | ||||
|           <span> | ||||
|             <Link to={`/@${username}`}> | ||||
|               <strong className='navigation-bar__profile-account'>@{username}</strong> | ||||
|             </Link> | ||||
|           </span> | ||||
| 
 | ||||
|           <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> | ||||
|           <span> | ||||
|             <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> | ||||
|           </span> | ||||
|         </div> | ||||
| 
 | ||||
|         <div className='navigation-bar__actions'> | ||||
|           <IconButton className='close' title='' icon='close' onClick={this.props.onClose} /> | ||||
|           <ActionBar account={this.props.account} onLogout={this.props.onLogout} /> | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -196,8 +196,8 @@ class Status extends ImmutablePureComponent { | |||
|     dispatch: PropTypes.func.isRequired, | ||||
|     status: ImmutablePropTypes.map, | ||||
|     isLoading: PropTypes.bool, | ||||
|     ancestorsIds: ImmutablePropTypes.list, | ||||
|     descendantsIds: ImmutablePropTypes.list, | ||||
|     ancestorsIds: ImmutablePropTypes.list.isRequired, | ||||
|     descendantsIds: ImmutablePropTypes.list.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     askReplyConfirmation: PropTypes.bool, | ||||
|     multiColumn: PropTypes.bool, | ||||
|  | @ -224,14 +224,9 @@ class Status extends ImmutablePureComponent { | |||
| 
 | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { | ||||
|       this._scrolledIntoView = false; | ||||
|       this.props.dispatch(fetchStatus(nextProps.params.statusId)); | ||||
|     } | ||||
| 
 | ||||
|     if (nextProps.params.statusId && nextProps.ancestorsIds.size > this.props.ancestorsIds.size) { | ||||
|       this._scrolledIntoView = false; | ||||
|     } | ||||
| 
 | ||||
|     if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) { | ||||
|       this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') }); | ||||
|     } | ||||
|  | @ -584,20 +579,23 @@ class Status extends ImmutablePureComponent { | |||
|     this.node = c; | ||||
|   }; | ||||
| 
 | ||||
|   componentDidUpdate () { | ||||
|     if (this._scrolledIntoView) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { status, ancestorsIds } = this.props; | ||||
| 
 | ||||
|     if (status && ancestorsIds && ancestorsIds.size > 0) { | ||||
|       const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1]; | ||||
|   componentDidUpdate (prevProps) { | ||||
|     const { status, ancestorsIds, multiColumn } = this.props; | ||||
| 
 | ||||
|     if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) { | ||||
|       window.requestAnimationFrame(() => { | ||||
|         element.scrollIntoView(true); | ||||
|         this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true); | ||||
| 
 | ||||
|         // In the single-column interface, `scrollIntoView` will put the post behind the header, | ||||
|         // so compensate for that. | ||||
|         if (!multiColumn) { | ||||
|           const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; | ||||
|           if (offset) { | ||||
|             const scrollingElement = document.scrollingElement || document.body; | ||||
|             scrollingElement.scrollBy(0, -offset); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|       this._scrolledIntoView = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -161,13 +161,20 @@ body { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   &:focus { | ||||
|     border-radius: 4px; | ||||
|     outline: $ui-button-icon-focus-outline; | ||||
|   } | ||||
| 
 | ||||
|   &:focus:not(:focus-visible) { | ||||
|     outline: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| button { | ||||
|   font-family: inherit; | ||||
|   cursor: pointer; | ||||
| 
 | ||||
|   &:focus { | ||||
|     outline: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .app-holder { | ||||
|  |  | |||
|  | @ -74,6 +74,10 @@ | |||
|     background-color: $ui-button-focus-background-color; | ||||
|   } | ||||
| 
 | ||||
|   &:focus { | ||||
|     outline: $ui-button-icon-focus-outline; | ||||
|   } | ||||
| 
 | ||||
|   &--destructive { | ||||
|     &:active, | ||||
|     &:focus, | ||||
|  | @ -98,16 +102,6 @@ | |||
|     transition: none; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner { | ||||
|     border: 0; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner, | ||||
|   &:focus, | ||||
|   &:active { | ||||
|     outline: 0 !important; | ||||
|   } | ||||
| 
 | ||||
|   &.button-secondary { | ||||
|     color: $ui-button-secondary-color; | ||||
|     background: transparent; | ||||
|  | @ -197,7 +191,7 @@ | |||
|   border-radius: 4px; | ||||
|   background: transparent; | ||||
|   cursor: pointer; | ||||
|   transition: all 100ms ease-in; | ||||
|   transition: all 100ms ease-out; | ||||
|   transition-property: background-color, color; | ||||
|   text-decoration: none; | ||||
| 
 | ||||
|  | @ -209,14 +203,12 @@ | |||
|   &:hover, | ||||
|   &:active, | ||||
|   &:focus { | ||||
|     color: lighten($action-button-color, 7%); | ||||
|     background-color: rgba($action-button-color, 0.15); | ||||
|     transition: all 200ms ease-out; | ||||
|     transition-property: background-color, color; | ||||
|     color: lighten($action-button-color, 20%); | ||||
|     background-color: $ui-button-icon-hover-background-color; | ||||
|   } | ||||
| 
 | ||||
|   &:focus { | ||||
|     background-color: rgba($action-button-color, 0.3); | ||||
|     outline: $ui-button-icon-focus-outline; | ||||
|   } | ||||
| 
 | ||||
|   &.disabled { | ||||
|  | @ -225,20 +217,6 @@ | |||
|     cursor: default; | ||||
|   } | ||||
| 
 | ||||
|   &.active { | ||||
|     color: $highlight-text-color; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner { | ||||
|     border: 0; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner, | ||||
|   &:focus, | ||||
|   &:active { | ||||
|     outline: 0 !important; | ||||
|   } | ||||
| 
 | ||||
|   &.inverted { | ||||
|     color: $lighter-text-color; | ||||
| 
 | ||||
|  | @ -246,11 +224,11 @@ | |||
|     &:active, | ||||
|     &:focus { | ||||
|       color: darken($lighter-text-color, 7%); | ||||
|       background-color: rgba($lighter-text-color, 0.15); | ||||
|       background-color: $ui-button-icon-hover-background-color; | ||||
|     } | ||||
| 
 | ||||
|     &:focus { | ||||
|       background-color: rgba($lighter-text-color, 0.3); | ||||
|       outline: $ui-button-icon-focus-outline; | ||||
|     } | ||||
| 
 | ||||
|     &.disabled { | ||||
|  | @ -305,7 +283,6 @@ | |||
|   font-size: 11px; | ||||
|   padding: 0 3px; | ||||
|   line-height: 27px; | ||||
|   outline: 0; | ||||
|   transition: all 100ms ease-in; | ||||
|   transition-property: background-color, color; | ||||
| 
 | ||||
|  | @ -313,13 +290,13 @@ | |||
|   &:active, | ||||
|   &:focus { | ||||
|     color: darken($lighter-text-color, 7%); | ||||
|     background-color: rgba($lighter-text-color, 0.15); | ||||
|     background-color: $ui-button-icon-hover-background-color; | ||||
|     transition: all 200ms ease-out; | ||||
|     transition-property: background-color, color; | ||||
|   } | ||||
| 
 | ||||
|   &:focus { | ||||
|     background-color: rgba($lighter-text-color, 0.3); | ||||
|     outline: $ui-button-icon-focus-outline; | ||||
|   } | ||||
| 
 | ||||
|   &.disabled { | ||||
|  | @ -331,16 +308,6 @@ | |||
|   &.active { | ||||
|     color: $highlight-text-color; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner { | ||||
|     border: 0; | ||||
|   } | ||||
| 
 | ||||
|   &::-moz-focus-inner, | ||||
|   &:focus, | ||||
|   &:active { | ||||
|     outline: 0 !important; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| body > [data-popper-placement] { | ||||
|  | @ -728,7 +695,6 @@ body > [data-popper-placement] { | |||
|     flex: 0 0 auto; | ||||
| 
 | ||||
|     .compose-form__publish-button-wrapper { | ||||
|       overflow: hidden; | ||||
|       padding-top: 15px; | ||||
|     } | ||||
|   } | ||||
|  | @ -1929,13 +1895,6 @@ a.account__display-name { | |||
|   .navigation-bar__actions { | ||||
|     position: relative; | ||||
| 
 | ||||
|     .icon-button.close { | ||||
|       position: absolute; | ||||
|       pointer-events: none; | ||||
|       transform: scale(0, 1) translate(-100%, 0); | ||||
|       opacity: 0; | ||||
|     } | ||||
| 
 | ||||
|     .compose__action-bar .icon-button { | ||||
|       pointer-events: auto; | ||||
|       transform: scale(1, 1) translate(0, 0); | ||||
|  | @ -1945,19 +1904,21 @@ a.account__display-name { | |||
| } | ||||
| 
 | ||||
| .navigation-bar__profile { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   flex: 1 1 auto; | ||||
|   line-height: 20px; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .navigation-bar__profile-account { | ||||
|   display: block; | ||||
|   display: inline; | ||||
|   font-weight: 500; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
| } | ||||
| 
 | ||||
| .navigation-bar__profile-edit { | ||||
|   display: inline; | ||||
|   color: inherit; | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | @ -4740,11 +4701,6 @@ a.status-card.compact:hover { | |||
|   outline: 0; | ||||
|   cursor: pointer; | ||||
| 
 | ||||
|   &:active, | ||||
|   &:focus { | ||||
|     outline: 0 !important; | ||||
|   } | ||||
| 
 | ||||
|   img { | ||||
|     filter: grayscale(100%); | ||||
|     opacity: 0.8; | ||||
|  | @ -4760,6 +4716,13 @@ a.status-card.compact:hover { | |||
|     img { | ||||
|       opacity: 1; | ||||
|       filter: none; | ||||
|       border-radius: 100%; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   &:focus { | ||||
|     img { | ||||
|       outline: $ui-button-icon-focus-outline; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ $red-600: #b7253d !default; // Deep Carmine | |||
| $red-500: #df405a !default; // Cerise | ||||
| $blurple-600: #563acc; // Iris | ||||
| $blurple-500: #6364ff; // Brand purple | ||||
| $blurple-400: #7477fd; // Medium slate blue | ||||
| $blurple-300: #858afa; // Faded Blue | ||||
| $grey-600: #4e4c5a; // Trout | ||||
| $grey-100: #dadaf3; // Topaz | ||||
|  | @ -56,6 +57,9 @@ $ui-button-tertiary-focus-color: $white !default; | |||
| $ui-button-destructive-background-color: $red-500 !default; | ||||
| $ui-button-destructive-focus-background-color: $red-600 !default; | ||||
| 
 | ||||
| $ui-button-icon-focus-outline: solid 2px $blurple-400 !default; | ||||
| $ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default; | ||||
| 
 | ||||
| // Variables for texts | ||||
| $primary-text-color: $white !default; | ||||
| $darker-text-color: $ui-primary-color !default; | ||||
|  |  | |||
|  | @ -99,7 +99,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer | |||
|   end | ||||
| 
 | ||||
|   def name | ||||
|     object.suspended? ? '' : object.display_name | ||||
|     object.suspended? ? object.username : (object.display_name.presence || object.username) | ||||
|   end | ||||
| 
 | ||||
|   def summary | ||||
|  |  | |||
|  | @ -1,5 +1,10 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| # TODO: Remove after 4.2.0 | ||||
| Rails.application.configure do | ||||
|   config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1 | ||||
| end | ||||
| 
 | ||||
| Rails.application.config.after_initialize do | ||||
|   Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies| | ||||
|     authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt | ||||
|  | @ -7,8 +12,9 @@ Rails.application.config.after_initialize do | |||
| 
 | ||||
|     secret_key_base = Rails.application.secret_key_base | ||||
| 
 | ||||
|     # TODO: Switch to SHA1 after 4.2.0 | ||||
|     key_generator = ActiveSupport::KeyGenerator.new( | ||||
|       secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1 | ||||
|       secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA256 | ||||
|     ) | ||||
|     key_len = ActiveSupport::MessageEncryptor.key_len | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,43 +6,43 @@ RSpec.describe Vacuum::ApplicationsVacuum do | |||
|   subject { described_class.new } | ||||
| 
 | ||||
|   describe '#perform' do | ||||
|     let!(:app1) { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app2) { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app3) { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app4) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) } | ||||
|     let!(:app5) { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app6) { Fabricate(:application, created_at: 1.hour.ago) } | ||||
|     let!(:app_with_token)  { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app_with_grant)  { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app_with_signup) { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:app_with_owner)  { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) } | ||||
|     let!(:unused_app)      { Fabricate(:application, created_at: 1.month.ago) } | ||||
|     let!(:recent_app)      { Fabricate(:application, created_at: 1.hour.ago) } | ||||
| 
 | ||||
|     let!(:active_access_token) { Fabricate(:access_token, application: app1) } | ||||
|     let!(:active_access_grant) { Fabricate(:access_grant, application: app2) } | ||||
|     let!(:user) { Fabricate(:user, created_by_application: app3) } | ||||
|     let!(:active_access_token) { Fabricate(:access_token, application: app_with_token) } | ||||
|     let!(:active_access_grant) { Fabricate(:access_grant, application: app_with_grant) } | ||||
|     let!(:user) { Fabricate(:user, created_by_application: app_with_signup) } | ||||
| 
 | ||||
|     before do | ||||
|       subject.perform | ||||
|     end | ||||
| 
 | ||||
|     it 'does not delete applications with valid access tokens' do | ||||
|       expect { app1.reload }.to_not raise_error | ||||
|       expect { app_with_token.reload }.to_not raise_error | ||||
|     end | ||||
| 
 | ||||
|     it 'does not delete applications with valid access grants' do | ||||
|       expect { app2.reload }.to_not raise_error | ||||
|       expect { app_with_grant.reload }.to_not raise_error | ||||
|     end | ||||
| 
 | ||||
|     it 'does not delete applications that were used to create users' do | ||||
|       expect { app3.reload }.to_not raise_error | ||||
|       expect { app_with_signup.reload }.to_not raise_error | ||||
|     end | ||||
| 
 | ||||
|     it 'does not delete owned applications' do | ||||
|       expect { app4.reload }.to_not raise_error | ||||
|       expect { app_with_owner.reload }.to_not raise_error | ||||
|     end | ||||
| 
 | ||||
|     it 'does not delete applications registered less than a day ago' do | ||||
|       expect { app6.reload }.to_not raise_error | ||||
|       expect { recent_app.reload }.to_not raise_error | ||||
|     end | ||||
| 
 | ||||
|     it 'deletes unused applications' do | ||||
|       expect { app5.reload }.to raise_error ActiveRecord::RecordNotFound | ||||
|       expect { unused_app.reload }.to raise_error ActiveRecord::RecordNotFound | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| require 'rails_helper' | ||||
| 
 | ||||
| describe 'Content-Security-Policy' do | ||||
|   it 'sets the expected CSP headers' do | ||||
|     allow(SecureRandom).to receive(:base64).with(16).and_return('ZbA+JmE7+bK8F5qvADZHuQ==') | ||||
| 
 | ||||
|     get '/' | ||||
|     expect(response.headers['Content-Security-Policy'].split(';').map(&:strip)).to contain_exactly( | ||||
|       "base-uri 'none'", | ||||
|       "default-src 'none'", | ||||
|       "frame-ancestors 'none'", | ||||
|       "font-src 'self' https://cb6e6126.ngrok.io", | ||||
|       "img-src 'self' https: data: blob: https://cb6e6126.ngrok.io", | ||||
|       "style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='", | ||||
|       "media-src 'self' https: data: https://cb6e6126.ngrok.io", | ||||
|       "frame-src 'self' https:", | ||||
|       "manifest-src 'self' https://cb6e6126.ngrok.io", | ||||
|       "form-action 'self'", | ||||
|       "child-src 'self' blob: https://cb6e6126.ngrok.io", | ||||
|       "worker-src 'self' blob: https://cb6e6126.ngrok.io", | ||||
|       "connect-src 'self' data: blob: https://cb6e6126.ngrok.io https://cb6e6126.ngrok.io ws://localhost:4000", | ||||
|       "script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'" | ||||
|     ) | ||||
|   end | ||||
| end | ||||
		Loading…
	
		Reference in New Issue