@kilpi/core updates
Policies and deny() and grant()
deny() and grant() have been renamed to Deny() and Grant().
import { deny, grant } from "@kilpi/core"; import { Deny, Grant } from "@kilpi/core";
export const Kilpi = createKilpi({ policies: { async isAdmin(subject) { if (subject.role === "admin") return grant(subject); if (subject.role === "admin") return Grant(subject); return deny({ message: "User is not an admin" }); return Deny({ message: "User is not an admin" }); } }})Remove scope
Remove all references to scope. It is no longer a part of Kilpi with the simplifications made in v1.
These include Kilpi.runInScope, Kilpi.scoped and the Kilpi.hooks.onRequestScope hook.
return await Kilpi.runInScope(async () => { return await handler();}, req)
export const POST = Kilpi.scoped(async (req: Request) => { return await handler();})
await handler();
export const POST = async (req: Request) => { return await handler();} Instead of passing ctx via scope, you now pass it to the authorize() method directly.
await Kilpi.runInScope(async () => { await Kilpi.authorize("posts:edit", post);}, ctx);
await Kilpi.posts.edit(post).authorize({ ctx }); Kilpi.onUnauthorized
Remove all Kilpi.onUnauthorized calls.
Fall back to using the default onUnauthorizedAssert provided to Kilpi, the Kilpi.$hooks.onUnauthorizedAssert hooks or the .assert(() => { ... }) callback feature (or your own solution built on these).
createKilpi
settings.disableSubjectCaching has been removed and settings.defaultOnUnauthorized has been renamed to onUnauthorizedAssert.
export const Kilpi = createKilpi({ // ... settings: { async defaultOnUnauthorized(decision) { await redirect(`/sign-in?message=${decision.message}`); }, disableSubjectCaching: true, }, async onUnauthorizedAssert(decision) { await redirect(`/sign-in?message=${decision.message}`); },}) Kilpi.isAuthorized, Kilpi.authorize and Kilpi.getAuthorizationDecision
Update to the new .authorize() API.
const granted = await Kilpi.isAuthorized("posts:edit", post);const { granted } = await Kilpi.posts.edit(post).authorize();
const decision = await Kilpi.getAuthorizationDecision("posts:create");const decision = await Kilpi.posts.create().authorize();
const subject = await Kilpi.authorize("posts:delete", post);const { subject } = await Kilpi.posts.delete(post).authorize().assert(); Kilpi.getSubject
The Kilpi.getSubject method has been renamed to Kilpi.$getSubject.
const subject = await Kilpi.getSubject();const subject = await Kilpi.$getSubject(); Kilpi.unauthorized()
The unauthorized() method has been removed, but you can mimic its behavior with a never policy.
export const Kilpi = createKilpi({ // ... policies: { never: () => Deny(), // ... }})
await Kilpi.unauthorized();await Kilpi.never().authorize().assert(); Kilpi.query
Kilpi.query is renamed to Kilpi.$query and the API has been updated.
const getPost = await Kilpi.query(const getPost = await Kilpi.$query( async (id) => { ... }, { async protector({ subject, input, output }) { ... }, async authorize({ subject, input, output }) { ... }, })
const post = await getPost.protect("1");const post = await getPost.authorized("1");
const post = await getPost.unsafe("1");const post = await getPost.unauthorized("1"); Kilpi.hooks
Kilpi.hooks is renamed to Kilpi.$hooks and the Kilpi.onRequestScope hook is removed.
EndpointPlugin
The EndpointPlugin’s Kilpi.createPostEndpoint method is renamed to Kilpi.$createPostEndpoint.
AuditPlugin
The AuditPlugin’s interface under Kilpi.audit has been renamed to Kilpi.$audit.
@kilpi/client updates
KilpiClient.fetchIsAuthorized
The old fetchIsAuthorized API is now more similar to the server-side API. It also returns the decision and not only a boolean.
const granted = await KilpiClient.fetchIsAuthorized({ action: "posts.edit", object: post, queryOptions: { signal },});const { granted } = await KilpiClient.posts.edit(post).authorize({ signal }); KilpiClient.fetchSubject
The fetchSubject method has been removed. You can mimic the old behavior with an always policy if needed.
export const Kilpi = createKilpi({ // ... policies: { always: (subject) => Grant(subject), // ... }})
const subject = await KilpiClient.fetchSubject();const decision = await KilpiClient.always().authorize();const subject = decision.granted ? decision.subject : null; // Should always be grantedKilpiClient.clearCache
Client-side cache invalidation has been improved with the new Kilpi.$cache API.
await KilpiClient.clearCache();await KilpiClient.$cache.invalidate(); @kilpi/react-server updates
ReactServerComponentPlugin
The ReactServerComponentPlugin has been renamed to ReactServerPlugin and the API has been updated.
import { ReactServerComponentPlugin } from "@kilpi/react-server";import { ReactServerPlugin } from "@kilpi/react-server";
export const Kilpi = createKilpi({ // ... plugins: [ReactServerComponentPlugin()], plugins: [ReactServerPlugin()],}) <Access /> component
The <Access /> component has been redesigned as the <Authorize /> component.
const { Access } = Kilpi.ReactServer.createComponents();const { Authorize } = Kilpi.$createReactServerComponents();
<Access to="posts:delete" on={post} Loading={<LoadingUI />} Unauthorized={<UnauthorizedUI />}> <DeletePostForm post={post} /></Access>
<Authorize policy={Kilpi.posts.delete(post)} Pending={<LoadingUI />} Unauthorized={<UnauthorizedUI />}> <DeletePostForm post={post} /></Authorize> Kilpi.onUnauthorized
Replace Kilpi.onUnauthorized calls with Kilpi.$onUnauthorizedRscAssert calls.
Kilpi.onUnauthorized(async (decision) => { // ...})Kilpi.$onUnauthorizedRscAssert(async (decision) => { // ...}) @kilpi/react-client updates
ReactClientComponentPlugin
The ReactClientComponentPlugin has been renamed to ReactClientPlugin and the API has been updated.
import { ReactClientComponentPlugin } from "@kilpi/react-client";import { ReactClientPlugin } from "@kilpi/react-client";
export const KilpiClient = createKilpiClient({ // ... plugins: [ReactClientComponentPlugin()], plugins: [ReactClientPlugin()],}) <ClientAccess /> component
const { ClientAccess } = KilpiClient.createComponents();const { AuthorizeClient } = KilpiClient.$createReactClientComponents();
<ClientAccess to="posts:delete" on={post} Loading={<LoadingUI />} Unauthorized={<UnauthorizedUI />} Error={<ErrorUI />}> <DeletePostForm post={post} /></ClientAccess>
<AuthorizeClient policy={KilpiClient.posts.delete(post)} Pending={<LoadingUI />} Unauthorized={<UnauthorizedUI />} Error={<ErrorUI />}> <DeletePostForm post={post} /></AuthorizeClient> useIsAuthorized() hook
The useIsAuthorized hook has been renamed to useAuthorize() and is available on each policy separately.
const { useIsAuthorized } = KilpiClient.ReactClient.createComponents();
const { isAuthorized, status, error, // ...} = useIsAuthorized("comments:edit", comment);
const { granted, status, error, // ...} = KilpiClient.comments.edit(comment).useAuthorize(); useSubject() hook
The useSubject hook has been removed. You can mimic the old behavior with an always policy if needed.
const { useSubject } = KilpiClient.ReactClient.createComponents();
const { subject, status, error, // ...} = useSubject();
export const KilpiClient = createKilpiClient({ // ... policies: { always: (subject) => Grant(subject), // ... }})
function useSubject() { const query = KilpiClient.always().useAuthorize(); return { ...query, subject: query.granted ? query.decision.subject : null };}
const { subject, status, error, // ...} = useSubject();