diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 5837b36..264d40f 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -37,7 +37,7 @@ jobs: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} - source: "dist/*,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,ecosystem.config.cjs" + source: "dist/*,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,ecosystem.config.cjs,prisma/schema.prisma,prisma/migrations,prisma.config.ts" target: "${{ env.TARGET_DIR }}" - name: Restart PM2 @@ -52,9 +52,9 @@ jobs: [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" npm install -g pnpm@11 2>&1 cd ${{ env.TARGET_DIR }} - pnpm install --prod 2>&1 cat > .env <<'ENVEOF' DATABASE_URL="${{ secrets.DATABASE_URL }}" + DIRECT_URL="${{ secrets.DIRECT_URL }}" GOOGLE_SERVICE_ACCOUNT_EMAIL="${{ secrets.GOOGLE_SERVICE_ACCOUNT_EMAIL }}" GOOGLE_PRIVATE_KEY="${{ secrets.GOOGLE_PRIVATE_KEY }}" GOOGLE_SHEET_ID="${{ secrets.GOOGLE_SHEET_ID }}" @@ -65,5 +65,8 @@ jobs: CLOUDFLARE_ACCOUNT_ID="${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" ADMIN_API_KEY="${{ secrets.ADMIN_API_KEY }}" ENVEOF + pnpm install --prod 2>&1 + pnpm exec prisma generate 2>&1 + pnpm exec prisma migrate deploy 2>&1 pm2 reload ecosystem.config.cjs --only ${{ env.APP_NAME }} --update-env || \ pm2 start ecosystem.config.cjs --only ${{ env.APP_NAME }} --update-env diff --git a/package.json b/package.json index ebab4fd..8e56ca6 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.19", - "daisyui": "^5.5.18" + "daisyui": "^5.5.18", + "dotenv": "^17.4.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4932aba..1ed8852 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,6 +102,9 @@ importers: daisyui: specifier: ^5.5.18 version: 5.5.20 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 packages: diff --git a/src/pages/api/admin/auth/login.ts b/src/pages/api/admin/auth/login.ts index cb73729..0897031 100644 --- a/src/pages/api/admin/auth/login.ts +++ b/src/pages/api/admin/auth/login.ts @@ -1,11 +1,23 @@ import type { APIRoute } from "astro"; import { prisma } from "../../lib/prisma"; -import { createSession, sessionCookieFor, verifyPassword } from "../../lib/auth"; +import { + createSession, + isSameOrigin, + sessionCookieFor, + verifyPassword, +} from "../../lib/auth"; export const prerender = false; export const POST: APIRoute = async ({ request }) => { try { + if (!isSameOrigin(request)) { + return Response.json( + { success: false, message: "Origen no válido" }, + { status: 403 } + ); + } + const body = await request.json(); const identifier = String(body?.username || body?.email || "") .trim() diff --git a/src/pages/api/admin/auth/logout.ts b/src/pages/api/admin/auth/logout.ts index ef533df..ee0fd43 100644 --- a/src/pages/api/admin/auth/logout.ts +++ b/src/pages/api/admin/auth/logout.ts @@ -1,9 +1,13 @@ import type { APIRoute } from "astro"; -import { clearSessionCookie, revokeSession } from "../../lib/auth"; +import { clearSessionCookie, isSameOrigin, revokeSession } from "../../lib/auth"; export const prerender = false; export const POST: APIRoute = async ({ request }) => { + if (!isSameOrigin(request)) { + return new Response(null, { status: 403 }); + } + await revokeSession(request); return new Response(null, { diff --git a/src/pages/api/lib/auth.ts b/src/pages/api/lib/auth.ts index 7e51c83..bce0536 100644 --- a/src/pages/api/lib/auth.ts +++ b/src/pages/api/lib/auth.ts @@ -69,6 +69,9 @@ function readSessionToken(request: Request): string | null { export async function createSession(adminId: string): Promise { const token = randomBytes(32).toString("base64url"); + await prisma.session.deleteMany({ + where: { expires_at: { lte: new Date() } }, + }); await prisma.session.create({ data: { token_hash: sha256(token), @@ -83,25 +86,30 @@ export async function getSession(request: Request): Promise const token = readSessionToken(request); if (!token) return null; - const session = await prisma.session.findUnique({ - where: { token_hash: sha256(token) }, - include: { admin: true }, - }); + try { + const session = await prisma.session.findUnique({ + where: { token_hash: sha256(token) }, + include: { admin: true }, + }); - if ( - !session || - session.expires_at.getTime() <= Date.now() || - !session.admin.activo - ) { + if ( + !session || + session.expires_at.getTime() <= Date.now() || + !session.admin.activo + ) { + return null; + } + + return { + adminId: session.admin.id, + email: session.admin.email, + nombre: session.admin.nombre, + rol: session.admin.rol, + }; + } catch (error) { + console.error("Error al leer la sesión:", error); return null; } - - return { - adminId: session.admin.id, - email: session.admin.email, - nombre: session.admin.nombre, - rol: session.admin.rol, - }; } export async function revokeSession(request: Request): Promise { @@ -127,3 +135,17 @@ export async function isApiAuthorized(request: Request): Promise { return timingSafeEqualStr(key, apiKey); } + +export function isSameOrigin(request: Request): boolean { + const origin = request.headers.get("origin"); + const referer = request.headers.get("referer"); + const host = request.headers.get("host"); + if (!host) return true; + try { + if (origin) return new URL(origin).host === host; + if (referer) return new URL(referer).host === host; + } catch { + return false; + } + return true; +}