Compare commits

...

4 Commits

Author SHA1 Message Date
Kalista Payne 956b6ca021 fix(logging): use SRI for Loggly 2026-06-05 18:12:32 -05:00
Kalista Payne 106ea68e31 fix(test): add expected CSP header 2026-06-04 11:25:40 -05:00
Fiz e6eda1bdaa fix(tasks): correct move route for challenge task reordering (#15660)
route challenge task moves to the user endpoint, not group-tasks
2026-06-04 11:20:36 -05:00
Kalista Payne bca081d7be fix(csp): do it ourself 2026-06-04 11:17:37 -05:00
5 changed files with 92 additions and 3 deletions
+2
View File
@@ -23,6 +23,7 @@ describe('cors middleware', () => {
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
'Content-Security-Policy': "default-src 'self' habitica.com *.habitica.com *.amazon.com *.amazonaws.com *.amplitude.com *.loggly.com *.payments-amazon.com *.stripe.com *.stripe.network; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src * data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'",
});
expect(res.sendStatus).to.not.have.been.called;
expect(next).to.have.been.calledOnce;
@@ -36,6 +37,7 @@ describe('cors middleware', () => {
'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE',
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
'Content-Security-Policy': "default-src 'self' habitica.com *.habitica.com *.amazon.com *.amazonaws.com *.amplitude.com *.loggly.com *.payments-amazon.com *.stripe.com *.stripe.network; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src * data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'",
});
expect(res.sendStatus).to.have.been.calledWith(200);
expect(next).to.not.have.been.called;
+7 -1
View File
@@ -30,7 +30,13 @@
<div id="app"></div>
<script type="text/javascript" src="//cloudfront.loggly.com/js/loggly.tracker-latest.min.js" async></script>
<script
type="text/javascript"
src="https://cloudfront.loggly.com/js/loggly.tracker-2.2.4.min.js"
integrity="sha384-ZvM/0vF3kE06VouL+Bw9hXzsu35aY1S+Ke5QMdziFtODNWO/+/bq9IiWZ+EAvIVn"
crossorigin="anonymous"
async
></script>
<!-- Translations -->
<script type='text/javascript' src='/api/v4/i18n/core' vite-ignore></script>
</body>
@@ -582,7 +582,7 @@ export default {
const newPosition = where === 'top' ? 0 : list.length;
list.splice(newPosition, 0, moved[0]);
if (!this.isUser) {
if (task.group.id && !this.isUser) {
await this.$store.dispatch('tasks:moveGroupTask', {
taskId: taskIdToMove,
position: newPosition,
@@ -592,7 +592,7 @@ export default {
taskId: taskIdToMove,
position: newPosition,
});
this.user.tasksOrder[`${this.type}s`] = newOrder;
if (!this.taskListOverride) this.user.tasksOrder[`${this.type}s`] = newOrder;
}
},
async rewardSorted (data) {
@@ -237,5 +237,84 @@ describe('Task Column', () => {
});
});
});
// each board type should hit the right move route
describe('moveTo (task ordering route)', () => {
function makeStore (userData = {}, extraGetters = {}) {
return new Store({
getters: {
'tasks:getFilteredTaskList': () => () => [],
'tasks:getUnfilteredTaskList': () => () => [],
...extraGetters,
},
state: {
user: {
data: {
preferences: { tasks: { activeFilter: {} } },
tasksOrder: { habits: [] },
...userData,
},
},
},
});
}
function stubDispatch (vm) {
const calls = [];
vm.$store.dispatch = (action, payload) => {
calls.push({ action, payload });
return Promise.resolve(['b', 'a']);
};
return calls;
}
test('challenge tasks (no group.id) use tasks:move and keep the user order untouched', async () => {
wrapper = makeWrapper({ store: makeStore() });
wrapper.setProps({
isUser: false,
challenge: { _id: 'c1' },
taskListOverride: [{ _id: 'a', group: {} }, { _id: 'b', group: {} }],
});
const calls = stubDispatch(wrapper.vm);
await wrapper.vm.moveTo({ _id: 'a', group: {} }, 'bottom');
expect(calls).to.have.lengthOf(1);
expect(calls[0].action).to.eq('tasks:move');
// an overridden list must never overwrite the user's personal order
expect(wrapper.vm.user.tasksOrder.habits.join(',')).to.eq('');
});
test('group-plan tasks (with group.id) use tasks:moveGroupTask', async () => {
wrapper = makeWrapper({ store: makeStore() });
wrapper.setProps({
isUser: false,
group: { _id: 'g1' },
taskListOverride: [{ _id: 'a', group: { id: 'g1' } }, { _id: 'b', group: { id: 'g1' } }],
});
const calls = stubDispatch(wrapper.vm);
await wrapper.vm.moveTo({ _id: 'a', group: { id: 'g1' } }, 'bottom');
expect(calls).to.have.lengthOf(1);
expect(calls[0].action).to.eq('tasks:moveGroupTask');
});
test('user tasks use tasks:move and update the user order', async () => {
const store = makeStore(
{ tasksOrder: { habits: ['a', 'b'] } },
{ 'tasks:getUnfilteredTaskList': () => () => [{ _id: 'a', group: {} }, { _id: 'b', group: {} }] },
);
wrapper = makeWrapper({ store });
wrapper.setProps({ isUser: true });
const calls = stubDispatch(wrapper.vm);
await wrapper.vm.moveTo({ _id: 'a', group: {} }, 'bottom');
expect(calls).to.have.lengthOf(1);
expect(calls[0].action).to.eq('tasks:move');
expect(wrapper.vm.user.tasksOrder.habits.join(',')).to.eq('b,a');
});
});
});
});
+2
View File
@@ -5,6 +5,8 @@ export default function corsMiddleware (req, res, next) {
'Access-Control-Allow-Headers': 'Authorization,Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key,x-client',
// Expose rate limit headers to CORS requests
'Access-Control-Expose-Headers': 'X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After',
// Content-Security-Policy based on Helmet defaults
'Content-Security-Policy': "default-src 'self' habitica.com *.habitica.com *.amazon.com *.amazonaws.com *.amplitude.com *.loggly.com *.payments-amazon.com *.stripe.com *.stripe.network; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src * data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'",
});
if (req.method === 'OPTIONS') return res.sendStatus(200);
return next();