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,
|
onKeyPress: this.handleKeyPress,
|
||||||
}) : (
|
}) : (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={icon}
|
icon={!open ? icon : 'close'}
|
||||||
title={title}
|
title={title}
|
||||||
active={open}
|
active={open}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import { IconButton } from '../../../components/icon_button';
|
|
||||||
|
|
||||||
import ActionBar from './action_bar';
|
import ActionBar from './action_bar';
|
||||||
|
|
||||||
|
@ -21,23 +20,27 @@ export default class NavigationBar extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const username = this.props.account.get('acct')
|
||||||
return (
|
return (
|
||||||
<div className='navigation-bar'>
|
<div className='navigation-bar'>
|
||||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
<Link to={`/@${username}`}>
|
||||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
<span style={{ display: 'none' }}>{username}</span>
|
||||||
<Avatar account={this.props.account} size={46} />
|
<Avatar account={this.props.account} size={46} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className='navigation-bar__profile'>
|
<div className='navigation-bar__profile'>
|
||||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
<span>
|
||||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
|
<Link to={`/@${username}`}>
|
||||||
|
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
||||||
</Link>
|
</Link>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='navigation-bar__actions'>
|
<div className='navigation-bar__actions'>
|
||||||
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
|
|
||||||
<ActionBar account={this.props.account} onLogout={this.props.onLogout} />
|
<ActionBar account={this.props.account} onLogout={this.props.onLogout} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -196,8 +196,8 @@ class Status extends ImmutablePureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
ancestorsIds: ImmutablePropTypes.list,
|
ancestorsIds: ImmutablePropTypes.list.isRequired,
|
||||||
descendantsIds: ImmutablePropTypes.list,
|
descendantsIds: ImmutablePropTypes.list.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
askReplyConfirmation: PropTypes.bool,
|
askReplyConfirmation: PropTypes.bool,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
|
@ -224,14 +224,9 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||||
this._scrolledIntoView = false;
|
|
||||||
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
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) {
|
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
|
||||||
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
|
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
|
||||||
}
|
}
|
||||||
|
@ -584,20 +579,23 @@ class Status extends ImmutablePureComponent {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate (prevProps) {
|
||||||
if (this._scrolledIntoView) {
|
const { status, ancestorsIds, multiColumn } = this.props;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { status, ancestorsIds } = this.props;
|
|
||||||
|
|
||||||
if (status && ancestorsIds && ancestorsIds.size > 0) {
|
|
||||||
const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
|
|
||||||
|
|
||||||
|
if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) {
|
||||||
window.requestAnimationFrame(() => {
|
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 {
|
button {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-holder {
|
.app-holder {
|
||||||
|
|
|
@ -74,6 +74,10 @@
|
||||||
background-color: $ui-button-focus-background-color;
|
background-color: $ui-button-focus-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: $ui-button-icon-focus-outline;
|
||||||
|
}
|
||||||
|
|
||||||
&--destructive {
|
&--destructive {
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
|
@ -98,16 +102,6 @@
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.button-secondary {
|
&.button-secondary {
|
||||||
color: $ui-button-secondary-color;
|
color: $ui-button-secondary-color;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
@ -197,7 +191,7 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 100ms ease-in;
|
transition: all 100ms ease-out;
|
||||||
transition-property: background-color, color;
|
transition-property: background-color, color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
@ -209,14 +203,12 @@
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: lighten($action-button-color, 7%);
|
color: lighten($action-button-color, 20%);
|
||||||
background-color: rgba($action-button-color, 0.15);
|
background-color: $ui-button-icon-hover-background-color;
|
||||||
transition: all 200ms ease-out;
|
|
||||||
transition-property: background-color, color;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: rgba($action-button-color, 0.3);
|
outline: $ui-button-icon-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
@ -225,20 +217,6 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: $highlight-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inverted {
|
&.inverted {
|
||||||
color: $lighter-text-color;
|
color: $lighter-text-color;
|
||||||
|
|
||||||
|
@ -246,11 +224,11 @@
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: darken($lighter-text-color, 7%);
|
color: darken($lighter-text-color, 7%);
|
||||||
background-color: rgba($lighter-text-color, 0.15);
|
background-color: $ui-button-icon-hover-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: rgba($lighter-text-color, 0.3);
|
outline: $ui-button-icon-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
@ -305,7 +283,6 @@
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
line-height: 27px;
|
line-height: 27px;
|
||||||
outline: 0;
|
|
||||||
transition: all 100ms ease-in;
|
transition: all 100ms ease-in;
|
||||||
transition-property: background-color, color;
|
transition-property: background-color, color;
|
||||||
|
|
||||||
|
@ -313,13 +290,13 @@
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: darken($lighter-text-color, 7%);
|
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: all 200ms ease-out;
|
||||||
transition-property: background-color, color;
|
transition-property: background-color, color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: rgba($lighter-text-color, 0.3);
|
outline: $ui-button-icon-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
@ -331,16 +308,6 @@
|
||||||
&.active {
|
&.active {
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body > [data-popper-placement] {
|
body > [data-popper-placement] {
|
||||||
|
@ -728,7 +695,6 @@ body > [data-popper-placement] {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
.compose-form__publish-button-wrapper {
|
.compose-form__publish-button-wrapper {
|
||||||
overflow: hidden;
|
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1929,13 +1895,6 @@ a.account__display-name {
|
||||||
.navigation-bar__actions {
|
.navigation-bar__actions {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.icon-button.close {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
transform: scale(0, 1) translate(-100%, 0);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compose__action-bar .icon-button {
|
.compose__action-bar .icon-button {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
transform: scale(1, 1) translate(0, 0);
|
transform: scale(1, 1) translate(0, 0);
|
||||||
|
@ -1945,19 +1904,21 @@ a.account__display-name {
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__profile {
|
.navigation-bar__profile {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__profile-account {
|
.navigation-bar__profile-account {
|
||||||
display: block;
|
display: inline;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation-bar__profile-edit {
|
.navigation-bar__profile-edit {
|
||||||
|
display: inline;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -4740,11 +4701,6 @@ a.status-card.compact:hover {
|
||||||
outline: 0;
|
outline: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
@ -4760,6 +4716,13 @@ a.status-card.compact:hover {
|
||||||
img {
|
img {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
filter: none;
|
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
|
$red-500: #df405a !default; // Cerise
|
||||||
$blurple-600: #563acc; // Iris
|
$blurple-600: #563acc; // Iris
|
||||||
$blurple-500: #6364ff; // Brand purple
|
$blurple-500: #6364ff; // Brand purple
|
||||||
|
$blurple-400: #7477fd; // Medium slate blue
|
||||||
$blurple-300: #858afa; // Faded Blue
|
$blurple-300: #858afa; // Faded Blue
|
||||||
$grey-600: #4e4c5a; // Trout
|
$grey-600: #4e4c5a; // Trout
|
||||||
$grey-100: #dadaf3; // Topaz
|
$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-background-color: $red-500 !default;
|
||||||
$ui-button-destructive-focus-background-color: $red-600 !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
|
// Variables for texts
|
||||||
$primary-text-color: $white !default;
|
$primary-text-color: $white !default;
|
||||||
$darker-text-color: $ui-primary-color !default;
|
$darker-text-color: $ui-primary-color !default;
|
||||||
|
|
|
@ -99,7 +99,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
object.suspended? ? '' : object.display_name
|
object.suspended? ? object.username : (object.display_name.presence || object.username)
|
||||||
end
|
end
|
||||||
|
|
||||||
def summary
|
def summary
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# 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.after_initialize do
|
||||||
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
|
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
|
||||||
authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt
|
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
|
secret_key_base = Rails.application.secret_key_base
|
||||||
|
|
||||||
|
# TODO: Switch to SHA1 after 4.2.0
|
||||||
key_generator = ActiveSupport::KeyGenerator.new(
|
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
|
key_len = ActiveSupport::MessageEncryptor.key_len
|
||||||
|
|
||||||
|
|
|
@ -6,43 +6,43 @@ RSpec.describe Vacuum::ApplicationsVacuum do
|
||||||
subject { described_class.new }
|
subject { described_class.new }
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
let!(:app1) { Fabricate(:application, created_at: 1.month.ago) }
|
let!(:app_with_token) { Fabricate(:application, created_at: 1.month.ago) }
|
||||||
let!(:app2) { Fabricate(:application, created_at: 1.month.ago) }
|
let!(:app_with_grant) { Fabricate(:application, created_at: 1.month.ago) }
|
||||||
let!(:app3) { Fabricate(:application, created_at: 1.month.ago) }
|
let!(:app_with_signup) { Fabricate(:application, created_at: 1.month.ago) }
|
||||||
let!(:app4) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) }
|
let!(:app_with_owner) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) }
|
||||||
let!(:app5) { Fabricate(:application, created_at: 1.month.ago) }
|
let!(:unused_app) { Fabricate(:application, created_at: 1.month.ago) }
|
||||||
let!(:app6) { Fabricate(:application, created_at: 1.hour.ago) }
|
let!(:recent_app) { Fabricate(:application, created_at: 1.hour.ago) }
|
||||||
|
|
||||||
let!(:active_access_token) { Fabricate(:access_token, application: app1) }
|
let!(:active_access_token) { Fabricate(:access_token, application: app_with_token) }
|
||||||
let!(:active_access_grant) { Fabricate(:access_grant, application: app2) }
|
let!(:active_access_grant) { Fabricate(:access_grant, application: app_with_grant) }
|
||||||
let!(:user) { Fabricate(:user, created_by_application: app3) }
|
let!(:user) { Fabricate(:user, created_by_application: app_with_signup) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
subject.perform
|
subject.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not delete applications with valid access tokens' do
|
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
|
end
|
||||||
|
|
||||||
it 'does not delete applications with valid access grants' do
|
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
|
end
|
||||||
|
|
||||||
it 'does not delete applications that were used to create users' do
|
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
|
end
|
||||||
|
|
||||||
it 'does not delete owned applications' do
|
it 'does not delete owned applications' do
|
||||||
expect { app4.reload }.to_not raise_error
|
expect { app_with_owner.reload }.to_not raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not delete applications registered less than a day ago' do
|
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
|
end
|
||||||
|
|
||||||
it 'deletes unused applications' do
|
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
|
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