v4: a handle with this instance's own host names the local user — another instance writes bob from here as bob@gerbeur.example, so a mention or a link copied from there named no one here: /@bob@gerbeur.example was a 404, and the mention notified nobody. localHandle (shared/validation.ts) drops this instance's host, its API's or its pages', from a handle; resolveUsername reads names by it, so the page, the API and the Open Graph 301 reach bob at /@bob, and so do the mention notifications and the chat's highlight and chime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-09 22:11:39 +00:00
parent 6dfba07341
commit 9608520675
11 changed files with 79 additions and 12 deletions

View File

@@ -25,6 +25,8 @@ import {
import { type Page, pageOf } from "../lib/pagination.ts";
import { sendToUser } from "./ws-service.ts";
import { mentionedUsernames } from "../../shared/mention.ts";
import { localHandle } from "../../shared/validation.ts";
import { isSelfHost } from "../lib/self-url.ts";
/** Each id of a user a notification names, beside the field of their name. */
const USER_FIELDS = [
@@ -359,7 +361,7 @@ export function notifyMentions(
for (const username of usernames) {
const mentionedRow = db.prepare(
`SELECT id FROM users WHERE lower(username) = ?;`,
).get(username) as { id: string } | undefined;
).get(localHandle(username, isSelfHost)) as { id: string } | undefined;
if (!mentionedRow || mentionedRow.id === mentionerUserId) continue;
if (skipUserId?.(mentionedRow.id)) continue;

View File

@@ -39,7 +39,12 @@ import { LIKE, likePattern } from "../lib/like.ts";
import { parseRows } from "../model/rows.ts";
import { isEmailEnabled, sendEmail } from "./email-service.ts";
import { renderWelcomeEmail } from "../mail/welcome.ts";
import { USERNAME_RE, VALIDATION } from "../../shared/validation.ts";
import {
localHandle,
USERNAME_RE,
VALIDATION,
} from "../../shared/validation.ts";
import { isSelfHost } from "../lib/self-url.ts";
import { EVERYONE } from "../../shared/mention.ts";
/** Selects full `UserRow`s from `users u`, with the inviter's name joined. */
@@ -235,17 +240,19 @@ export function getUserByUsername(username: string): User {
}
/**
* Returns the user who is or was named `username`, in any case: a page or an
* API path naming a user by a former name reaches them, and the caller sends
* the client on to the current one.
* Returns the user who is or was named `username`, in any case, or by the
* handle another instance gives them (`localHandle`): a page or an API path
* naming a user so reaches them, and the caller sends the client on to their
* current name.
*
* @throws {APIException} 404 when nobody is or was.
*/
export function resolveUsername(username: string): User {
const name = localHandle(username, isSelfHost);
const alias = db.prepare(
`SELECT user_id FROM user_aliases WHERE lower(username) = lower(?);`,
).get(username) as { user_id: string } | undefined;
return alias ? getUserById(alias.user_id) : getUserByUsername(username);
).get(name) as { user_id: string } | undefined;
return alias ? getUserById(alias.user_id) : getUserByUsername(name);
}
/**