・プッシュ通知の修正
・メニューをスマホに最適化 ・アラート送信済みの条件が再度発動しないように修正
This commit is contained in:
@@ -18,6 +18,7 @@ const USER_ID = '11111111-1111-4111-8111-111111111111';
|
||||
const SITE_ID = '22222222-2222-4222-8222-222222222222';
|
||||
const ALERT_ID = '33333333-3333-4333-8333-333333333333';
|
||||
const WEBHOOK_ID = '44444444-4444-4444-8444-444444444444';
|
||||
const PUSH_ENDPOINT = 'https://push.example.com/subscription';
|
||||
|
||||
function authCookie() {
|
||||
return 'certremind_session=session-1';
|
||||
@@ -143,6 +144,182 @@ describe('API security boundaries', () => {
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not expose registered push subscription lists', async () => {
|
||||
mockAuthenticatedUser();
|
||||
query.mockImplementationOnce(query.getMockImplementation()).mockImplementationOnce(async (sql, params) => {
|
||||
expect(sql).toContain('FROM notification_methods');
|
||||
expect(sql).toContain("notification_type = 'webhook'");
|
||||
expect(params).toEqual([USER_ID]);
|
||||
return {
|
||||
rows: [
|
||||
{
|
||||
notification_method_id: WEBHOOK_ID,
|
||||
alias: 'Deploy hook',
|
||||
url: 'https://hooks.example.com/',
|
||||
created_at: '2026-05-20T00:00:00.000Z',
|
||||
updated_at: '2026-05-21T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods', {
|
||||
headers: {
|
||||
Cookie: authCookie(),
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
const data = await response.json();
|
||||
expect(data.webhooks).toHaveLength(1);
|
||||
expect(data).not.toHaveProperty('pushSubscriptions');
|
||||
});
|
||||
|
||||
it('reports the current browser push subscription as registered for the session user', async () => {
|
||||
mockAuthenticatedUser();
|
||||
query.mockImplementationOnce(query.getMockImplementation()).mockImplementationOnce(async (sql, params) => {
|
||||
expect(sql).toContain('FROM notification_methods');
|
||||
expect(sql).toContain("notification_type = 'push'");
|
||||
expect(params).toEqual([USER_ID, PUSH_ENDPOINT]);
|
||||
return { rows: [{ notification_method_id: '66666666-6666-4666-8666-666666666666' }] };
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscription-status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: PUSH_ENDPOINT }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({ registered: true });
|
||||
});
|
||||
|
||||
it('reports push subscriptions for other users or unknown endpoints as unregistered', async () => {
|
||||
mockAuthenticatedUser();
|
||||
query.mockImplementationOnce(query.getMockImplementation()).mockImplementationOnce(async (sql, params) => {
|
||||
expect(sql).toContain('FROM notification_methods');
|
||||
expect(params).toEqual([USER_ID, PUSH_ENDPOINT]);
|
||||
return { rows: [] };
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscription-status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: PUSH_ENDPOINT }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({ registered: false });
|
||||
});
|
||||
|
||||
it('rejects non-HTTPS push subscription status endpoints', async () => {
|
||||
mockAuthenticatedUser();
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscription-status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: 'http://push.example.com/subscription' }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('requires a CSRF token when deleting push subscriptions', async () => {
|
||||
const app = createApp();
|
||||
|
||||
const response = await app.request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: PUSH_ENDPOINT }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(query).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deletes the current user push subscription by endpoint', async () => {
|
||||
mockAuthenticatedUser();
|
||||
query.mockImplementationOnce(query.getMockImplementation()).mockImplementationOnce(async (sql, params) => {
|
||||
expect(sql).toContain('DELETE FROM notification_methods');
|
||||
expect(sql).toContain("notification_type = 'push'");
|
||||
expect(params).toEqual([USER_ID, PUSH_ENDPOINT]);
|
||||
return { rows: [{ notification_method_id: '66666666-6666-4666-8666-666666666666' }] };
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: PUSH_ENDPOINT }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
await expect(response.json()).resolves.toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('does not delete push subscriptions owned by other users or unknown endpoints', async () => {
|
||||
mockAuthenticatedUser();
|
||||
query.mockImplementationOnce(query.getMockImplementation()).mockImplementationOnce(async (sql, params) => {
|
||||
expect(sql).toContain('DELETE FROM notification_methods');
|
||||
expect(params).toEqual([USER_ID, PUSH_ENDPOINT]);
|
||||
return { rows: [] };
|
||||
});
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: PUSH_ENDPOINT }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it('rejects non-HTTPS push subscription delete endpoints', async () => {
|
||||
mockAuthenticatedUser();
|
||||
|
||||
const app = createApp();
|
||||
const response = await app.request('/api/notification-methods/push-subscriptions', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: csrfCookie(),
|
||||
'x-csrf-token': 'csrf-token',
|
||||
},
|
||||
body: JSON.stringify({ endpoint: 'http://push.example.com/subscription' }),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('stores the initial certificate metadata when creating a site', async () => {
|
||||
const issuer = 'C = US, O = Example CA, CN = Example Root';
|
||||
const issuedAt = new Date('2026-01-01T00:00:00.000Z');
|
||||
|
||||
Reference in New Issue
Block a user