mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import type {ApiContext} from '@app/api/ApiContext';
|
|
import * as AuthPassword from '@app/api/auth/AuthPassword';
|
|
import * as AuthSession from '@app/api/auth/AuthSession';
|
|
import * as AuthUtility from '@app/api/auth/AuthUtility';
|
|
import {createEmailRevertToken} from '@app/api/BrandedTypes';
|
|
import type {User} from '@app/api/models/User';
|
|
import {enqueueStripeCustomerEmailSync} from '@app/api/stripe/StripeCustomer';
|
|
import {mapUserToPrivateResponse} from '@app/api/user/UserMappers';
|
|
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
|
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
|
import {requireClientIp} from '@fluxer/ip_utils/src/ClientIp';
|
|
|
|
interface IssueEmailRevertTokenParams {
|
|
user: User;
|
|
previousEmail: string;
|
|
newEmail: string;
|
|
}
|
|
|
|
interface RevertEmailChangeParams {
|
|
token: string;
|
|
password: string;
|
|
request: Request;
|
|
}
|
|
|
|
export async function issueEmailRevertToken(ctx: ApiContext, params: IssueEmailRevertTokenParams): Promise<void> {
|
|
const {users, email} = ctx.services;
|
|
const {user, previousEmail, newEmail} = params;
|
|
const trimmed = previousEmail.trim();
|
|
if (!trimmed) return;
|
|
const token = createEmailRevertToken(await AuthUtility.generateSecureToken(ctx));
|
|
await users.createEmailRevertToken({
|
|
token_: token,
|
|
user_id: user.id,
|
|
email: trimmed,
|
|
});
|
|
await email.sendEmailChangeRevert(trimmed, user.username, newEmail, token, user.locale);
|
|
}
|
|
|
|
export async function revertEmailChange(
|
|
ctx: ApiContext,
|
|
params: RevertEmailChangeParams,
|
|
): Promise<{
|
|
user_id: string;
|
|
token: string;
|
|
}> {
|
|
const {users, gateway, contactChangeLog, config, worker} = ctx.services;
|
|
const {token, password, request} = params;
|
|
const tokenData = await users.getEmailRevertToken(token);
|
|
if (!tokenData) {
|
|
throw InputValidationError.fromCode('token', ValidationErrorCodes.INVALID_OR_EXPIRED_REVERT_TOKEN);
|
|
}
|
|
const user = await users.findUnique(tokenData.userId);
|
|
if (!user) {
|
|
throw InputValidationError.fromCode('token', ValidationErrorCodes.INVALID_OR_EXPIRED_REVERT_TOKEN);
|
|
}
|
|
AuthUtility.assertNonBotUser(ctx, user);
|
|
await AuthUtility.handleBanStatus(ctx, user);
|
|
if (await AuthPassword.isPasswordPwned(ctx, password)) {
|
|
throw InputValidationError.fromCode('password', ValidationErrorCodes.PASSWORD_IS_TOO_COMMON);
|
|
}
|
|
const passwordHash = await AuthPassword.hashPassword(ctx, password);
|
|
const now = new Date();
|
|
const updatedUser = await users.patchUpsert(
|
|
user.id,
|
|
{
|
|
email: tokenData.email,
|
|
email_verified: true,
|
|
totp_secret: null,
|
|
authenticator_types: null,
|
|
password_hash: passwordHash,
|
|
password_last_changed_at: now,
|
|
},
|
|
user.toRow(),
|
|
);
|
|
await users.deleteAllPasswordResetTokens(user.id);
|
|
await users.deleteEmailRevertToken(token);
|
|
await users.deleteAllMfaBackupCodes(user.id);
|
|
await users.deleteAllWebAuthnCredentials(user.id);
|
|
await users.deleteAllAuthorizedIps(user.id);
|
|
await AuthSession.terminateAllUserSessions(ctx, user.id);
|
|
await users.createAuthorizedIp(
|
|
user.id,
|
|
requireClientIp(request, {
|
|
trustClientIpHeader: config.proxy.trust_client_ip_header,
|
|
clientIpHeaderName: config.proxy.client_ip_header,
|
|
}),
|
|
);
|
|
await gateway.dispatchPresence({
|
|
userId: updatedUser.id,
|
|
event: 'USER_UPDATE',
|
|
data: mapUserToPrivateResponse(updatedUser),
|
|
});
|
|
const [authToken] = await AuthSession.createAuthSession(ctx, {
|
|
user: updatedUser,
|
|
origin: AuthSession.resolveSessionOrigin(ctx, request),
|
|
});
|
|
await contactChangeLog.recordDiff({
|
|
oldUser: user,
|
|
newUser: updatedUser,
|
|
reason: 'user_requested',
|
|
actorUserId: user.id,
|
|
});
|
|
await enqueueStripeCustomerEmailSync(worker, user, updatedUser);
|
|
return {user_id: updatedUser.id.toString(), token: authToken};
|
|
}
|