This repository has been archived on 2026-03-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
drive/keto/namespaces.ts
Sienna Meridian Satterwhite 58237d9e44 Initial commit — Drive, an S3 file browser with WOPI editing
Lightweight replacement for the upstream La Suite Numérique drive
(Django/Celery/Next.js) built as a single Deno binary.

Server (Deno + Hono):
- S3 file operations via AWS SigV4 (no SDK) with pre-signed URLs
- WOPI host for Collabora Online (CheckFileInfo, GetFile, PutFile, locks)
- Ory Kratos session auth + CSRF protection
- Ory Keto permission model (OPL namespaces, not yet wired to routes)
- PostgreSQL metadata with recursive folder sizes
- S3 backfill API for registering files uploaded outside the UI
- OpenTelemetry tracing + metrics (opt-in via OTEL_ENABLED)

Frontend (React 19 + Cunningham v4 + react-aria):
- File browser with GridList, keyboard nav, multi-select
- Collabora editor iframe (full-screen, form POST, postMessage)
- Profile menu, waffle menu, drag-drop upload, asset type badges
- La Suite integration service theming (runtime CSS)

Testing (549 tests):
- 235 server unit tests (Deno) — 90%+ coverage
- 278 UI unit tests (Vitest) — 90%+ coverage
- 11 E2E tests (Playwright)
- 12 integration service tests (Playwright)
- 13 WOPI integration tests (Playwright + Docker Compose + Collabora)

MIT licensed.
2026-03-25 18:28:37 +00:00

121 lines
3.5 KiB
TypeScript

/**
* Ory Keto OPL (Ory Permission Language) namespace definitions.
*
* This file defines the permission model deployed to Keto.
* It is NOT executed by Deno — it uses Keto's OPL type system
* (Namespace, Context, SubjectSet) and is consumed by the Keto
* server at deploy time.
*
* Hierarchy: Bucket → Folder → File
* Permissions cascade downward through `parents` relations.
*/
// deno-lint-ignore-file no-unused-vars
/* ── Namespace / Context / SubjectSet are Keto OPL built-ins ── */
/* They are declared here so the file type-checks as valid TS. */
interface Namespace {
related?: Record<string, unknown[]>;
permits?: Record<string, (ctx: Context) => boolean>;
}
interface Context {
subject: unknown;
}
type SubjectSet<N, R extends string> = unknown;
// ─── Namespaces ──────────────────────────────────────────────────────────────
class User implements Namespace {}
class Group implements Namespace {
related: {
members: (User | Group)[];
};
}
class Bucket implements Namespace {
related: {
owners: User[];
editors: (User | SubjectSet<Group, "members">)[];
viewers: (User | SubjectSet<Group, "members">)[];
};
permits = {
write: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.editors.includes(ctx.subject),
read: (ctx: Context): boolean =>
this.permits.write(ctx) ||
this.related.viewers.includes(ctx.subject),
delete: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject),
};
}
class Folder implements Namespace {
related: {
owners: User[];
editors: (User | SubjectSet<Group, "members">)[];
viewers: (User | SubjectSet<Group, "members">)[];
parents: (Folder | Bucket)[];
};
permits = {
write: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.editors.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder | Bucket) =>
p.permits.write(ctx),
),
read: (ctx: Context): boolean =>
this.permits.write(ctx) ||
this.related.viewers.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder | Bucket) =>
p.permits.read(ctx),
),
delete: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder | Bucket) =>
p.permits.delete(ctx),
),
};
}
class File implements Namespace {
related: {
owners: User[];
editors: (User | SubjectSet<Group, "members">)[];
viewers: (User | SubjectSet<Group, "members">)[];
parents: Folder[];
};
permits = {
write: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.editors.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder) => p.permits.write(ctx)),
read: (ctx: Context): boolean =>
this.permits.write(ctx) ||
this.related.viewers.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder) => p.permits.read(ctx)),
delete: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
// @ts-ignore — Keto OPL traverse
this.related.parents.traverse((p: Folder) => p.permits.delete(ctx)),
};
}