alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
386 lines (334 loc) • 10.3 kB
text/typescript
import { randomUUID } from "node:crypto";
import { Alepha } from "alepha";
import {
$action,
$route,
AlephaServer,
ForbiddenError,
ServerProvider,
} from "alepha/server";
import { describe, expect, it } from "vitest";
import { $issuer, $secure, AlephaSecurity } from "../index.ts";
describe("$secure issuer protection", () => {
it("should allow access when user belongs to the required issuer", async () => {
class TestApp {
realmA = $issuer({
secret: "test-realm-a",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
realmB = $issuer({
secret: "test-realm-b",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
// Action that requires realmA
actionA = $action({
use: [$secure({ issuers: ["realmA"] })],
handler: () => "REALM_A",
});
// Route that requires realmB
routeB = $route({
method: "GET",
path: "/realm-b",
use: [$secure({ issuers: ["realmB"] })],
handler: () => "REALM_B",
});
}
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
const app = alepha.inject(TestApp);
await alepha.start();
const userA = {
id: randomUUID(),
roles: ["user"],
realm: "realmA",
name: "Test User A",
};
const userB = {
id: randomUUID(),
roles: ["user"],
realm: "realmB",
name: "Test User B",
};
// User from realmA should access actionA via .run()
expect(await app.actionA.run({}, { user: userA })).toBe("REALM_A");
// User from realmA should access actionA via .fetch()
expect(
await app.actionA.fetch({}, { user: userA }).then((it) => it.data),
).toBe("REALM_A");
// User from realmB should access routeB via HTTP
const tokenB = await app.realmB.createToken(userB);
const responseB = await fetch(
`${alepha.inject(ServerProvider).hostname}/realm-b`,
{
headers: {
authorization: `Bearer ${tokenB.access_token}`,
},
},
);
expect(await responseB.text()).toBe("REALM_B");
});
it("should deny access when user does not belong to the required issuer", async () => {
class TestApp {
realmA = $issuer({
secret: "test-realm-a",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
realmB = $issuer({
secret: "test-realm-b",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
// Action that requires realmA
actionA = $action({
use: [$secure({ issuers: ["realmA"] })],
handler: () => "REALM_A",
});
// Route that requires realmB
routeB = $route({
method: "GET",
path: "/realm-b",
use: [$secure({ issuers: ["realmB"] })],
handler: () => "REALM_B",
});
}
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
const app = alepha.inject(TestApp);
await alepha.start();
const userA = {
id: randomUUID(),
roles: ["user"],
realm: "realmA",
name: "Test User A",
};
const userB = {
id: randomUUID(),
roles: ["user"],
realm: "realmB",
name: "Test User B",
};
// User from realmB should NOT access actionA via .run()
await expect(app.actionA.run({}, { user: userB })).rejects.toThrowError(
ForbiddenError,
);
// User from realmB should NOT access actionA via .fetch()
await expect(app.actionA.fetch({}, { user: userB })).rejects.toThrowError(
"User must belong to issuer 'realmA' to access this route",
);
// User from realmA should NOT access routeB via HTTP (requires realmB)
const tokenA = await app.realmA.createToken(userA);
const responseB = await fetch(
`${alepha.inject(ServerProvider).hostname}/realm-b`,
{
headers: {
authorization: `Bearer ${tokenA.access_token}`,
},
},
);
expect(responseB.status).toBe(403);
const errorData = await responseB.json();
expect(errorData).toEqual({
error: "ForbiddenError",
message: "User must belong to issuer 'realmB' to access this route",
status: 403,
requestId: expect.any(String),
});
});
it("should work with actions when user has no issuer attribute", async () => {
class TestApp {
realmA = $issuer({
secret: "test-realm-a",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
actionA = $action({
use: [$secure({ issuers: ["realmA"] })],
handler: () => "REALM_A",
});
}
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
const app = alepha.inject(TestApp);
await alepha.start();
const userWithoutRealm = {
id: randomUUID(),
roles: ["user"],
// no realm attribute
};
// User without realm attribute should be denied via .run()
await expect(
app.actionA.run({}, { user: userWithoutRealm }),
).rejects.toThrowError(ForbiddenError);
// Note: .fetch() in test mode auto-assigns the first realm during token creation,
// so a user without a realm will succeed if the first realm matches the required realm.
// This is expected test helper behavior for convenience.
expect(
await app.actionA
.fetch({}, { user: userWithoutRealm })
.then((it) => it.data),
).toBe("REALM_A");
});
it("should combine issuer and permission checks", async () => {
class TestApp {
realmA = $issuer({
secret: "test-realm-a",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
{
name: "user",
permissions: [{ name: "read:*" }],
},
],
});
realmB = $issuer({
secret: "test-realm-b",
});
// Requires both realmA and admin:manage permission
adminAction = $action({
use: [$secure({ issuers: ["realmA"], permissions: ["admin:manage"] })],
handler: () => "ADMIN",
});
// Requires both realmA and read:data permission
readAction = $action({
use: [$secure({ issuers: ["realmA"], permissions: ["read:data"] })],
handler: () => "READ",
});
}
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
const app = alepha.inject(TestApp);
await alepha.start();
const adminUserRealmA = {
id: randomUUID(),
roles: ["admin"],
realm: "realmA",
name: "Admin User",
};
const regularUserRealmA = {
id: randomUUID(),
roles: ["user"],
realm: "realmA",
name: "Regular User",
};
const adminUserRealmB = {
id: randomUUID(),
roles: ["admin"],
realm: "realmB",
name: "Admin User B",
};
// Admin from realmA should access both actions via .run()
expect(await app.adminAction.run({}, { user: adminUserRealmA })).toBe(
"ADMIN",
);
expect(await app.readAction.run({}, { user: adminUserRealmA })).toBe(
"READ",
);
// Admin from realmA should access both actions via .fetch()
expect(
await app.adminAction
.fetch({}, { user: adminUserRealmA })
.then((it) => it.data),
).toBe("ADMIN");
expect(
await app.readAction
.fetch({}, { user: adminUserRealmA })
.then((it) => it.data),
).toBe("READ");
// Regular user from realmA can access readAction (has read:* permission)
expect(await app.readAction.run({}, { user: regularUserRealmA })).toBe(
"READ",
);
expect(
await app.readAction
.fetch({}, { user: regularUserRealmA })
.then((it) => it.data),
).toBe("READ");
// Regular user from realmA cannot access adminAction (no admin:* permission)
await expect(
app.adminAction.run({}, { user: regularUserRealmA }),
).rejects.toThrowError(ForbiddenError);
// Admin from realmB should NOT access any action via .run() (wrong realm)
await expect(
app.adminAction.run({}, { user: adminUserRealmB }),
).rejects.toThrowError(ForbiddenError);
await expect(
app.readAction.run({}, { user: adminUserRealmB }),
).rejects.toThrowError(ForbiddenError);
await expect(
app.adminAction.fetch({}, { user: adminUserRealmB }),
).rejects.toThrowError(
"User must belong to issuer 'realmA' to access this route",
);
await expect(
app.readAction.fetch({}, { user: adminUserRealmB }),
).rejects.toThrowError(
"User must belong to issuer 'realmA' to access this route",
);
});
it("should work with fetch requests when issuer is valid", async () => {
class TestApp {
realmA = $issuer({
secret: "test-realm-a",
roles: [
{
name: "user",
permissions: [{ name: "*" }],
},
],
});
actionA = $action({
use: [$secure({ issuers: ["realmA"] })],
handler: () => "SUCCESS",
});
}
const alepha = Alepha.create().with(AlephaServer).with(AlephaSecurity);
const app = alepha.inject(TestApp);
await alepha.start();
const user = {
id: randomUUID(),
roles: ["user"],
realm: "realmA",
name: "Test User",
};
// Should work via .run()
expect(await app.actionA.run({}, { user })).toBe("SUCCESS");
// Should work via .fetch()
expect(await app.actionA.fetch({}, { user }).then((it) => it.data)).toBe(
"SUCCESS",
);
// Should work via HTTP with token
const token = await app.realmA.createToken(user);
const response = await fetch(
`${alepha.inject(ServerProvider).hostname}${app.actionA.route.path}`,
{
headers: {
authorization: `Bearer ${token.access_token}`,
},
},
);
expect(response.status).toBe(200);
expect(await response.text()).toBe("SUCCESS");
});
});