Skip to content

Commit 8d71881

Browse files
committed
app/routes/crate/settings: hide the settings page for non-owners
The backend controllers all have appropriate permission checks, so there's no actual security issue here, but there's no reason for us to even show this page to someone who doesn't own a crate.
1 parent 4c4c268 commit 8d71881

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

app/routes/crate/settings.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
1+
import { inject as service } from '@ember/service';
2+
13
import AuthenticatedRoute from '../-authenticated-route';
24

35
export default class SettingsRoute extends AuthenticatedRoute {
6+
@service router;
7+
@service session;
8+
9+
async afterModel(crate, transition) {
10+
let user = this.session.currentUser;
11+
let owners = await crate.owner_user;
12+
let isOwner = owners.some(owner => owner.id === user.id);
13+
if (!isOwner) {
14+
this.router.replaceWith('catch-all', {
15+
transition,
16+
title: 'This page is only accessible by crate owners',
17+
});
18+
}
19+
}
20+
421
setupController(controller) {
522
super.setupController(...arguments);
623
let crate = this.modelFor('crate');
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { currentURL } from '@ember/test-helpers';
2+
import { module, test } from 'qunit';
3+
4+
import { setupApplicationTest } from 'crates-io/tests/helpers';
5+
6+
import { visit } from '../../helpers/visit-ignoring-abort';
7+
8+
module('Route | crate.settings', hooks => {
9+
setupApplicationTest(hooks, { msw: true });
10+
11+
function prepare(context) {
12+
const user = context.db.user.create();
13+
14+
const crate = context.db.crate.create({ name: 'foo' });
15+
context.db.version.create({ crate });
16+
context.db.crateOwnership.create({ crate, user });
17+
18+
return { crate, user };
19+
}
20+
21+
test('unauthenticated', async function (assert) {
22+
const crate = this.db.crate.create({ name: 'foo' });
23+
this.db.version.create({ crate });
24+
25+
await visit('/crates/foo/settings');
26+
assert.strictEqual(currentURL(), '/crates/foo/settings');
27+
assert.dom('[data-test-title]').hasText('This page requires authentication');
28+
assert.dom('[data-test-login]').exists();
29+
});
30+
31+
test('not an owner', async function (assert) {
32+
const { crate } = prepare(this);
33+
34+
const otherUser = this.db.user.create();
35+
this.authenticateAs(otherUser);
36+
37+
await visit(`/crates/${crate.name}/settings`);
38+
assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`);
39+
assert.dom('[data-test-title]').hasText('This page is only accessible by crate owners');
40+
assert.dom('[data-test-go-back]').exists();
41+
});
42+
43+
test('happy path', async function (assert) {
44+
const { crate, user } = prepare(this);
45+
this.authenticateAs(user);
46+
47+
await visit(`/crates/${crate.name}/settings`);
48+
assert.strictEqual(currentURL(), `/crates/${crate.name}/settings`);
49+
// This is the Add Owner button.
50+
assert.dom('[data-test-save-button]').exists();
51+
assert.dom('[data-test-owners]').exists();
52+
assert.dom(`[data-test-owner-user="${user.login}"]`).exists();
53+
assert.dom('[data-test-remove-owner-button]').exists();
54+
assert.dom('[data-test-delete-button]').exists();
55+
});
56+
});

0 commit comments

Comments
 (0)