[ticket/17339] Support receiving push notifications if not logged in

PHPBB-17339
This commit is contained in:
Marc Alexander 2024-06-12 22:16:41 +02:00
parent e2ff7a7178
commit d66d4a0c6a
No known key found for this signature in database
GPG key ID: 50E0D2423696F995
4 changed files with 143 additions and 6 deletions

View file

@ -0,0 +1,53 @@
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\db\migration\data\v400;
use phpbb\db\migration\migration;
class add_webpush_token extends migration
{
public static function depends_on(): array
{
return [
'\phpbb\db\migration\data\v400\add_webpush',
];
}
public function effectively_installed(): bool
{
return $this->db_tools->sql_column_exists($this->table_prefix . 'notification_push', 'push_token');
}
public function update_schema(): array
{
return [
'add_columns' => [
$this->table_prefix . 'notification_push' => [
'push_token' => ['VCHAR', ''],
],
],
];
}
public function revert_schema(): array
{
return [
'drop_columns' => [
$this->table_prefix . 'notification_push' => [
'push_token',
],
],
];
}
}

View file

@ -51,6 +51,9 @@ class webpush extends messenger_base implements extended_method_interface
/** @var int Fallback size for padding if endpoint is mozilla, see https://github.com/web-push-libs/web-push-php/issues/108#issuecomment-2133477054 */
const MOZILLA_FALLBACK_PADDING = 2820;
/** @var array Map for storing push token between db insertion and sending of notifications */
private array $push_token_map = [];
/**
* Notification Method Web Push constructor
*
@ -145,9 +148,11 @@ class webpush extends messenger_base implements extended_method_interface
'avatar' => $notification->get_avatar(),
]),
'notification_time' => time(),
'push_token' => hash('sha256', random_bytes(32))
];
$data = self::clean_data($data);
$insert_buffer->insert($data);
$this->push_token_map[$notification->notification_type_id][$notification->item_id] = $data['push_token'];
}
$insert_buffer->flush();
@ -221,7 +226,9 @@ class webpush extends messenger_base implements extended_method_interface
$data = [
'item_id' => $notification->item_id,
'type_id' => $notification->notification_type_id,
'user_id' => $notification->user_id,
'version' => $this->config['assets_version'],
'token' => hash('sha256', $user['user_form_salt'] . $this->push_token_map[$notification->notification_type_id][$notification->item_id]),
];
$json_data = json_encode($data);
@ -337,6 +344,7 @@ class webpush extends messenger_base implements extended_method_interface
'item_parent_id' => null,
'user_id' => null,
'push_data' => null,
'push_token' => null,
'notification_time' => null,
];

View file

@ -100,10 +100,37 @@ class webpush
* @return JsonResponse
*/
public function notification(): JsonResponse
{
if (!$this->request->is_ajax() || $this->user->data['is_bot'] || $this->user->data['user_type'] == USER_INACTIVE)
{
throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}
if ($this->user->id() !== ANONYMOUS)
{
$notification_data = $this->get_user_notifications();
}
else
{
$notification_data = $this->get_anonymous_notifications();
}
// Decode and return data if everything is fine
$data = json_decode($notification_data, true);
$data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : '';
return new JsonResponse($data);
}
/**
* Get notification data for logged in user
*
* @return string Notification data
*/
private function get_user_notifications(): string
{
// Subscribe should only be available for logged-in "normal" users
if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot']
|| $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE)
if ($this->user->data['user_type'] == USER_IGNORE)
{
throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}
@ -119,10 +146,53 @@ class webpush
$result = $this->db->sql_query($sql);
$notification_data = $this->db->sql_fetchfield('push_data');
$this->db->sql_freeresult($result);
$data = json_decode($notification_data, true);
$data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : '';
return new JsonResponse($data);
return $notification_data;
}
/**
* Get notification data for not logged in user via token
*
* @return string
*/
private function get_anonymous_notifications(): string
{
$token = $this->request->variable('token', '');
if ($token)
{
$item_id = $this->request->variable('item_id', 0);
$type_id = $this->request->variable('type_id', 0);
$user_id = $this->request->variable('user_id', 0);
$sql = 'SELECT push_data, push_token
FROM ' . $this->notification_webpush_table . '
WHERE user_id = ' . (int) $user_id . '
AND notification_type_id = ' . (int) $type_id . '
AND item_id = ' . (int) $item_id;
$result = $this->db->sql_query($sql);
$notification_row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$notification_data = $notification_row['push_data'];
$push_token = $notification_row['push_token'];
// Check if passed push token is valid
$sql = 'SELECT user_form_salt
FROM ' . USERS_TABLE . '
WHERE user_id = ' . (int) $user_id;
$result = $this->db->sql_query($sql);
$user_form_token = $this->db->sql_fetchfield('user_form_salt');
$this->db->sql_freeresult($result);
$expected_push_token = hash('sha256', $user_form_token . $push_token);
if ($expected_push_token === $token)
{
return $notification_data;
}
}
throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}
/**

View file

@ -23,12 +23,16 @@ self.addEventListener('push', event => {
let itemId = 0;
let typeId = 0;
let notificationVersion = 5;
let userId = 0;
let notificationVersion = 0;
let pushToken = '';
try {
const notificationData = event.data.json();
itemId = notificationData.item_id;
typeId = notificationData.type_id;
userId = notificationData.user_id;
notificationVersion = parseInt(notificationData.version, 10);
pushToken = notificationData.token;
} catch {
self.registration.showNotification(event.data.text());
return;
@ -45,6 +49,8 @@ self.addEventListener('push', event => {
const formData = new FormData();
formData.append('item_id', itemId.toString(10));
formData.append('type_id', typeId.toString(10));
formData.append('user_id', userId.toString(10));
formData.append('token', pushToken);
fetch(getNotificationUrl, {
method: 'POST',