halo-dev/halo · error · AccessDeniedException

You have no permission to delete this notification.

Error message

You have no permission to delete this notification.

What it means

Thrown as AccessDeniedException by DefaultNotificationService.deleteByName when the fetched Notification's spec.recipient does not equal the requesting username. Notifications are user-scoped; only the recipient may delete their own notification.

Source

Thrown at application/src/main/java/run/halo/app/notification/DefaultNotificationService.java:56

                    notification.getSpec().setLastReadAt(Instant.now());
                    return client.update(notification);
                });
    }

    @Override
    public Flux<String> markSpecifiedAsRead(String username, List<String> names) {
        return Flux.fromIterable(names)
                .flatMap(name -> markAsRead(username, name))
                .map(notification -> notification.getMetadata().getName());
    }

    @Override
    public Mono<Notification> deleteByName(String username, String name) {
        return client.get(Notification.class, name)
                .doOnNext(notification -> {
                    var recipient = notification.getSpec().getRecipient();
                    if (!username.equals(recipient)) {
                        throw new AccessDeniedException("You have no permission to delete this notification.");
                    }
                })
                .flatMap(client::delete);
    }

    static boolean isRecipient(Notification notification, String username) {
        Assert.notNull(notification, "Notification must not be null");
        Assert.notNull(username, "Username must not be null");
        return username.equals(notification.getSpec().getRecipient());
    }
}

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Ensure the client only issues deletes for notifications owned by the current user.
  2. Verify the notification name corresponds to the recipient before calling delete.
  3. If legitimate admin deletion is needed, use an admin-scoped API rather than the user endpoint.
  4. Check that the authenticated principal matches the expected username.

Example fix

// before
notificationService.deleteByName(currentUser, someName).block();

// after
Notification n = client.get(Notification.class, someName).block();
if (!currentUser.equals(n.getSpec().getRecipient())) {
    return Mono.error(new AccessDeniedException("Not owner"));
}
notificationService.deleteByName(currentUser, someName).block();
Defensive patterns

Strategy: validation

Validate before calling

Notification n = client.get(Notification.class, name).block();
if (n == null || !currentUser.equals(n.getSpec().getRecipient())) {
    return Mono.error(new AccessDeniedException("Not the recipient"));
}

Type guard

static boolean isOwner(Notification n, String user) {
    return n != null && n.getSpec() != null && user.equals(n.getSpec().getRecipient());
}

Try / catch

notificationService.deleteByName(currentUser, name)
    .onErrorResume(AccessDeniedException.class, e -> ServerResponse.status(HttpStatus.FORBIDDEN).build());

Prevention

When it happens

Trigger: An authenticated request to delete a notification whose spec.recipient is a different user than the current principal; e.g. a user guesses/iterates another user's notification name and calls delete.

Common situations: Client passing the wrong notification name; a shared/migrated account; frontend caching a stale notification name; a permission boundary bug where a non-owner action is exposed in the UI.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/b8ea0ba536a202b7. Report an issue: GitHub.