Ajoute le flux d'invitation avec activation de compte
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
|
||||
type AccountInviteContext = {
|
||||
verificationToken: { identifier: string; expires: Date };
|
||||
user: {
|
||||
id: string;
|
||||
email: string | null;
|
||||
name: string | null;
|
||||
passwordHash: string | null;
|
||||
emailVerified: Date | null;
|
||||
accounts: Array<{ id: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
type AccountInviteOptions = {
|
||||
prisma: any;
|
||||
tokenTtlMs?: number;
|
||||
identifierPrefix?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_ACCOUNT_INVITE_IDENTIFIER_PREFIX = "account-invite:";
|
||||
const DEFAULT_ACCOUNT_INVITE_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function hashAccountInviteToken(token: string): string {
|
||||
return createHash("sha256").update(token).digest("hex");
|
||||
}
|
||||
|
||||
function buildAccountInviteIdentifier(prefix: string, userId: string): string {
|
||||
return `${prefix}${userId}`;
|
||||
}
|
||||
|
||||
export async function createAccountInviteToken(
|
||||
options: AccountInviteOptions & { userId: string }
|
||||
): Promise<{ token: string; expiresAt: Date }> {
|
||||
const identifierPrefix = options.identifierPrefix ?? DEFAULT_ACCOUNT_INVITE_IDENTIFIER_PREFIX;
|
||||
const identifier = buildAccountInviteIdentifier(identifierPrefix, options.userId);
|
||||
const token = randomBytes(32).toString("hex");
|
||||
const expiresAt = new Date(Date.now() + (options.tokenTtlMs ?? DEFAULT_ACCOUNT_INVITE_TOKEN_TTL_MS));
|
||||
|
||||
await options.prisma.verificationToken.deleteMany({
|
||||
where: {
|
||||
OR: [{ identifier }, { expires: { lt: new Date() } }]
|
||||
}
|
||||
});
|
||||
|
||||
await options.prisma.verificationToken.create({
|
||||
data: {
|
||||
identifier,
|
||||
token: hashAccountInviteToken(token),
|
||||
expires: expiresAt
|
||||
}
|
||||
});
|
||||
|
||||
return { token, expiresAt };
|
||||
}
|
||||
|
||||
export async function getAccountInviteContext(
|
||||
options: AccountInviteOptions & { token: string }
|
||||
): Promise<AccountInviteContext | null> {
|
||||
const identifierPrefix = options.identifierPrefix ?? DEFAULT_ACCOUNT_INVITE_IDENTIFIER_PREFIX;
|
||||
const verificationToken = await options.prisma.verificationToken.findUnique({
|
||||
where: { token: hashAccountInviteToken(options.token) },
|
||||
select: { identifier: true, expires: true }
|
||||
});
|
||||
|
||||
if (
|
||||
!verificationToken ||
|
||||
verificationToken.expires <= new Date() ||
|
||||
!verificationToken.identifier.startsWith(identifierPrefix)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const userId = verificationToken.identifier.slice(identifierPrefix.length);
|
||||
if (!userId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await options.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
passwordHash: true,
|
||||
emailVerified: true,
|
||||
accounts: {
|
||||
select: { id: true },
|
||||
take: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!user?.email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
verificationToken,
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
DEFAULT_ACCOUNT_INVITE_IDENTIFIER_PREFIX,
|
||||
DEFAULT_ACCOUNT_INVITE_TOKEN_TTL_MS,
|
||||
buildAccountInviteIdentifier,
|
||||
hashAccountInviteToken
|
||||
};
|
||||
Reference in New Issue
Block a user