The nuxi typecheck command complains about type only imports that are not declared as such, but the VsCode environment does not. There's probably a missmatch somewhere in the configuration for Nuxt that I'm not going to dig into. Workaround this issue for now by setting the option in the tsconfig.json file for the server.
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
/*
|
|
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
import * as fs from "node:fs/promises";
|
|
import type { H3Event } from "h3";
|
|
import { z } from "zod/v4-mini";
|
|
import { readAuthenticationMethods, readUsers } from "~/server/database";
|
|
import { type TelegramAuthData, telegramAuthDataSchema } from "~/shared/types/telegram";
|
|
import type { ApiSession } from "~/shared/types/api";
|
|
|
|
const loginSchema = z.object({
|
|
authData: telegramAuthDataSchema,
|
|
});
|
|
|
|
let cachedTelegramConfig:
|
|
| undefined
|
|
| { enabled: false }
|
|
| { enabled: true, botUsername: string, secretKey: CryptoKey }
|
|
;
|
|
async function useTelegramConfig(event: H3Event) {
|
|
if (cachedTelegramConfig)
|
|
return cachedTelegramConfig;
|
|
|
|
const runtimeConfig = useRuntimeConfig(event);
|
|
if (!runtimeConfig.public.authTelegramEnabled) {
|
|
return cachedTelegramConfig = {
|
|
enabled: false,
|
|
};
|
|
}
|
|
if (!runtimeConfig.telegramBotTokenFile) throw new Error("NUXT_TELEGRAM_BOT_TOKEN_FILE not configured");
|
|
if (!runtimeConfig.public.telegramBotUsername) throw new Error("NUXT_PUBLIC_TELEGRAM_BOT_USERNAME not configured");
|
|
|
|
const botToken = await fs.readFile(runtimeConfig.telegramBotTokenFile);
|
|
const secretKey = await crypto.subtle.importKey(
|
|
"raw",
|
|
await crypto.subtle.digest("SHA-256", botToken),
|
|
{
|
|
name: "HMAC",
|
|
hash: "SHA-256",
|
|
},
|
|
false,
|
|
["verify"],
|
|
);
|
|
return cachedTelegramConfig = {
|
|
enabled: true,
|
|
botUsername: runtimeConfig.public.telegramBotUsername,
|
|
secretKey,
|
|
}
|
|
}
|
|
|
|
async function validateTelegramAuthData(authData: TelegramAuthData, key: CryptoKey) {
|
|
const { hash, ...checkData } = authData;
|
|
const checkString = Object.entries(checkData).map(([key, value]) => `${key}=${value}`).sort().join("\n");
|
|
const signature = Buffer.from(hash, "hex");
|
|
return await crypto.subtle.verify("HMAC", key, signature, Buffer.from(checkString));
|
|
}
|
|
|
|
export default defineEventHandler(async (event): Promise<ApiSession> => {
|
|
const { success, error, data } = loginSchema.safeParse(await readBody(event));
|
|
if (!success) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: "Bad Request",
|
|
message: z.prettifyError(error),
|
|
});
|
|
}
|
|
|
|
const config = await useTelegramConfig(event);
|
|
if (!config.enabled) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: "Forbidden",
|
|
message: "Telegram authentication is disabled",
|
|
});
|
|
}
|
|
|
|
if (!await validateTelegramAuthData(data.authData, config.secretKey)) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: "Forbidden",
|
|
message: "Validating authentication data failed",
|
|
});
|
|
}
|
|
|
|
const slug = String(data.authData.id);
|
|
const authMethods = await readAuthenticationMethods();
|
|
const method = authMethods.find(method => method.provider === "telegram" && method.slug === slug);
|
|
let session;
|
|
if (method) {
|
|
const users = await readUsers();
|
|
const account = users.find(user => !user.deleted && user.id === method.userId);
|
|
session = await setServerSession(event, account);
|
|
} else {
|
|
const name = data.authData.username ? "@" + data.authData.username : slug;
|
|
session = await setServerSession(event, undefined, "telegram", slug, name);
|
|
}
|
|
|
|
return await serverSessionToApi(event, session);
|
|
})
|