feat(auth): add username sign-in mode and recovery kits (#3215)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-05 14:10:07 +02:00
1 parent bf3d73a5f7
commit e1eecc3b6c
355 files changed
+52138 -24097

No files matched your search

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {createRoleIDSet, createUserID, type RoleID, type UserID} from '@app/api/BrandedTypes';
import {usesUsernameSignIn} from '@app/api/instance/AccountIdentityModeCache';
import type {Guild} from '@app/api/models/Guild';
import type {GuildMember} from '@app/api/models/GuildMember';
import type {User} from '@app/api/models/User';
@@ -39,12 +40,18 @@ function checkGuildVerification(params: VerificationParams): void {
if (memberRoles && memberRoles.size > 0) {
return;
}
if (!user.email) {
throw new GuildVerificationRequiredError('You need to claim your account to send messages in this guild.');
}
if (verificationLevel >= GuildVerificationLevel.LOW) {
if (!user.emailVerified) {
throw new GuildEmailVerificationRequiredError();
if (usesUsernameSignIn()) {
if (user.isUnclaimedAccount()) {
throw new GuildVerificationRequiredError('You need to claim your account to send messages in this guild.');
}
} else {
if (!user.email) {
throw new GuildVerificationRequiredError('You need to claim your account to send messages in this guild.');
}
if (verificationLevel >= GuildVerificationLevel.LOW) {
if (!user.emailVerified) {
throw new GuildEmailVerificationRequiredError();
}
}
}
if (verificationLevel >= GuildVerificationLevel.MEDIUM) {
@@ -5,16 +5,21 @@ import {transliterate as tr} from 'transliteration';
const MAX_USERNAME_LENGTH = 32;
function sanitizeDisplayName(globalName: string): string | null {
const trimmed = globalName.trim();
if (!trimmed) return null;
export function normaliseUsernameCandidate(value: string): string {
const trimmed = value.trim();
if (!trimmed) return '';
let sanitized = tr(trimmed);
sanitized = sanitized.replace(/[\s\-.]+/g, '_');
sanitized = sanitized.replace(/[^a-zA-Z0-9_]/g, '');
if (!sanitized) return null;
if (sanitized.length > MAX_USERNAME_LENGTH) {
sanitized = sanitized.substring(0, MAX_USERNAME_LENGTH);
}
return sanitized;
}
function sanitizeDisplayName(globalName: string): string | null {
const sanitized = normaliseUsernameCandidate(globalName);
if (!sanitized) return null;
const validation = UsernameType.safeParse(sanitized);
if (!validation.success) {
return null;