Permessi ABAC
Efesto integra CASL per il controllo degli accessi basato sugli attributi (ABAC). L'intero contributo del framework è piccolo e preciso:
- Dichiari una tupla
permission: [action, model]su un endpoint. - Tu costruisci un'ability CASL e la metti sulla request/context.
- Prima di eseguire l'handler, Efesto verifica l'ability rispetto alla tupla e rifiuta la richiesta se non è consentita.
Tutto qui. Non ci sono decoratori di permessi, gerarchie di ruoli o loader di database integrati in Efesto: quelli sono pattern che costruisci sopra, con CASL.
1. Costruisci l'ability (il tuo codice)
L'ability è tua da creare, di solito nel middleware di autenticazione, a partire
dall'utente autenticato. Entrambi gli stack se la aspettano su un campo di contesto
chiamato ability per default.
- Express
- Bun/Elysia
Allega l'ability a req in un middleware che viene eseguito prima di Efesto:
import { defineAbility } from "@casl/ability";
app.use((req, res, next) => {
req.ability = defineAbility((can) => {
can("readAll", "User");
can("readOne", "User");
can("create", "User");
});
next();
});
app.use("/api/v1", efesto({ /* ... */ }));
Ricava l'ability in setup, così è presente nel contesto di ogni rotta:
import { defineAbility } from "@casl/ability";
const app = efesto({
isProduction: false,
routesDir: `${import.meta.dir}/routes`,
prefix: "/api/v1",
setup: (root) =>
root.derive(() => ({
ability: defineAbility((can) => {
can("readAll", "Product");
can("create", "Product");
}),
})),
});
2. Dichiara il permesso sull'endpoint
La tupla è [action, model] su entrambi gli stack. Efesto la verifica con
ability.can(action, model) prima che l'handler venga eseguito.
- Express
- Bun/Elysia
Dichiara permission all'interno dell'oggetto _<method>Swagger del metodo. Viene
scritto nello Swagger generato e applicato a runtime:
import { BaseApiService, SwaggerOptions } from "efesto";
class Users extends BaseApiService {
constructor() {
super(__filename);
}
_getSwagger: SwaggerOptions = {
operationId: "getUsers",
permission: ["readAll", "User"],
responses: { 200: { content: { "application/json": { schema: { type: "array", items: "@User" } } } } },
};
_get(req, res) {
return res.json(listUsers());
}
}
export default Users;
Dichiara permission nel _<verb>Swagger del verbo. Efesto lo compila in un guard
beforeHandle:
import { BaseApiService, type Context, RouteValidation, SwaggerOptions, t } from "efesto/elysia";
export default class extends BaseApiService {
_getSwagger: SwaggerOptions = { summary: "List products", permission: ["readAll", "Product"] };
_get() {
return listProducts();
}
_postSwagger: SwaggerOptions = { permission: ["create", "Product"] };
_postValidation: RouteValidation = { body: t.Object({ name: t.String() }) };
_post({ body }: Context) {
return createProduct(body);
}
}
3. Cosa succede in caso di fallimento
- Express
- Bun/Elysia
Se l'ability non può eseguire l'azione, Efesto solleva un
ForbiddenError di CASL
prima che il metodo venga eseguito. Gestiscilo nel tuo errorMiddleware per modellare
la risposta.
Il guard compilato imposta lo stato a 403 e restituisce { "error": "Forbidden" }
prima che l'handler venga eseguito.
Su entrambi gli stack il controllo è applicato solo quando un'ability è presente nel
contesto. Se non ne alleghi mai una, permission è documentato ma non applicato. Il
guard di Elysia rispecchia intenzionalmente il comportamento di Express in questo caso.
Configurazione
Regola l'applicazione tramite configurazione. I default corrispondono alle descrizioni sopra.
- Express
- Bun/Elysia
Nell'oggetto config di Efesto:
config: {
abacPermissions: {
actions: ["readAll", "readOne", "create", "update", "delete"], // azioni documentate
models: ["User", "Product"], // modelli documentati
checkPermissionBeforeResolver: true, // applica prima dell'handler (default true)
reqAbilityField: "ability", // dove Efesto legge l'ability (default "ability")
},
}
| Proprietà | Tipo | Descrizione | Default |
|---|---|---|---|
actions | string[] | Azioni disponibili (tipizzazione/aiuto per la tupla) | — |
models | string[] | Modelli disponibili | — |
checkPermissionBeforeResolver | boolean | Applica prima che il metodo venga eseguito | true |
reqAbilityField | string | Campo di req che contiene l'ability | "ability" |
Passa abac a efesto():
efesto({
// ...
abac: {
abilityField: "ability", // campo del contesto che contiene l'ability (default "ability")
checkPermissionBeforeResolver: true, // applica prima dell'handler (default true)
},
});
Oltre le basi
Ruoli, regole di proprietà, finestre temporali e permessi basati su database sono tutti
possibili, ma sono funzionalità di CASL e logica della tua applicazione, non API di
Efesto. Li esprimi quando fai defineAbility (passo 1); Efesto si limita a verificare
l'ability risultante rispetto alla tupla dichiarata. Vedi la
documentazione di CASL per condizioni e regole a livello di campo.
L'ability viene di solito costruita nel tuo livello di autenticazione.