Piattaforma ITI TECHNOLOGY: monorepo, Medusa, API, sito, area riservata e deploy
Deploy stack su Proxmox / deploy (push) Failing after 3s
Deploy stack su Proxmox / deploy (push) Failing after 3s
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA), apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/; infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e workflow Gitea Actions per il deploy via SSH a ogni push su master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import jwt from "@fastify/jwt"
|
||||
import type { UserRole } from "@iti/shared"
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"
|
||||
import fp from "fastify-plugin"
|
||||
import { AppError } from "../errors.js"
|
||||
|
||||
/** Payload dell'access token: solo id e ruolo (niente dati personali). */
|
||||
export type AccessTokenPayload = { sub: string; role: UserRole }
|
||||
|
||||
declare module "@fastify/jwt" {
|
||||
interface FastifyJWT {
|
||||
payload: AccessTokenPayload
|
||||
user: AccessTokenPayload
|
||||
}
|
||||
}
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyInstance {
|
||||
/** onRequest/preHandler: richiede un access token valido (Authorization: Bearer ...). */
|
||||
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
||||
/**
|
||||
* onRequest/preHandler per le route pubbliche che cambiano con l'utente (shop): senza header
|
||||
* Authorization la richiesta è anonima; con un token non valido o scaduto → 401 (il frontend
|
||||
* fa il refresh invece di ricevere silenziosamente i dati da anonimo).
|
||||
*/
|
||||
optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
||||
/** onRequest/preHandler: autentica e richiede uno dei ruoli indicati (403 altrimenti). */
|
||||
requireRole: (...roles: UserRole[]) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
||||
/** Firma un access token per l'utente (durata ACCESS_TOKEN_TTL_SECONDS). */
|
||||
signAccessToken: (payload: AccessTokenPayload) => string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access token JWT HS256 di breve durata, restituito nel body e tenuto in memoria dal frontend.
|
||||
* Il ruolo nel token vale fino alla scadenza: un cambio di ruolo (es. installatore approvato)
|
||||
* si riflette al refresh successivo, che rilegge l'utente dal DB.
|
||||
*/
|
||||
export default fp(
|
||||
async function authPlugin(app: FastifyInstance) {
|
||||
await app.register(jwt, {
|
||||
secret: app.config.JWT_SECRET,
|
||||
sign: { algorithm: "HS256", expiresIn: app.config.ACCESS_TOKEN_TTL_SECONDS },
|
||||
verify: { algorithms: ["HS256"] },
|
||||
messages: {
|
||||
noAuthorizationInHeaderMessage: "Autenticazione richiesta",
|
||||
authorizationTokenExpiredMessage: "Sessione scaduta",
|
||||
authorizationTokenInvalid: "Token non valido",
|
||||
authorizationTokenUntrusted: "Token non valido",
|
||||
authorizationTokenUnsigned: "Token non valido",
|
||||
badRequestErrorMessage: "Formato del token non valido",
|
||||
badCookieRequestErrorMessage: "Formato del token non valido",
|
||||
},
|
||||
})
|
||||
|
||||
app.decorate("signAccessToken", (payload: AccessTokenPayload) => app.jwt.sign(payload))
|
||||
|
||||
app.decorate("authenticate", async (request: FastifyRequest) => {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Autenticazione richiesta"
|
||||
throw new AppError(401, "UNAUTHORIZED", message)
|
||||
}
|
||||
})
|
||||
|
||||
app.decorate("optionalAuthenticate", async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
if (request.headers.authorization) await app.authenticate(request, reply)
|
||||
})
|
||||
|
||||
app.decorate("requireRole", (...roles: UserRole[]) => {
|
||||
return async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
if (!request.user) await app.authenticate(request, reply)
|
||||
if (!roles.includes(request.user.role)) {
|
||||
throw new AppError(403, "FORBIDDEN", "Permessi insufficienti")
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{ name: "auth", dependencies: ["config"] },
|
||||
)
|
||||
Reference in New Issue
Block a user