Skip to content

Commit c0a903d

Browse files
Merge pull request #1161 from CodeForAfrica/ft/trustlab-rbac
feat(access): add role-based access control and permissions
2 parents e9fdae5 + 1f3f910 commit c0a903d

File tree

19 files changed

+440
-161
lines changed

19 files changed

+440
-161
lines changed

apps/trustlab/payload-types.ts

Lines changed: 134 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,19 @@ export interface Config {
6767
};
6868
blocks: {};
6969
collections: {
70-
pages: Page;
7170
media: Media;
71+
pages: Page;
72+
posts: Post;
7273
users: User;
7374
"payload-locked-documents": PayloadLockedDocument;
7475
"payload-preferences": PayloadPreference;
7576
"payload-migrations": PayloadMigration;
7677
};
7778
collectionsJoins: {};
7879
collectionsSelect: {
79-
pages: PagesSelect<false> | PagesSelect<true>;
8080
media: MediaSelect<false> | MediaSelect<true>;
81+
pages: PagesSelect<false> | PagesSelect<true>;
82+
posts: PostsSelect<false> | PostsSelect<true>;
8183
users: UsersSelect<false> | UsersSelect<true>;
8284
"payload-locked-documents":
8385
| PayloadLockedDocumentsSelect<false>
@@ -125,69 +127,14 @@ export interface UserAuthOperations {
125127
password: string;
126128
};
127129
}
128-
/**
129-
* This interface was referenced by `Config`'s JSON-Schema
130-
* via the `definition` "pages".
131-
*/
132-
export interface Page {
133-
id: string;
134-
title: string;
135-
fullTitle?: string | null;
136-
slug?: string | null;
137-
blocks?:
138-
| {
139-
title: string;
140-
content: {
141-
root: {
142-
type: string;
143-
children: {
144-
type: string;
145-
version: number;
146-
[k: string]: unknown;
147-
}[];
148-
direction: ("ltr" | "rtl") | null;
149-
format:
150-
| "left"
151-
| "start"
152-
| "center"
153-
| "right"
154-
| "end"
155-
| "justify"
156-
| "";
157-
indent: number;
158-
version: number;
159-
};
160-
[k: string]: unknown;
161-
};
162-
id?: string | null;
163-
blockName?: string | null;
164-
blockType: "test";
165-
}[]
166-
| null;
167-
parent?: (string | null) | Page;
168-
breadcrumbs?:
169-
| {
170-
doc?: (string | null) | Page;
171-
url?: string | null;
172-
label?: string | null;
173-
id?: string | null;
174-
}[]
175-
| null;
176-
meta?: {
177-
title?: string | null;
178-
description?: string | null;
179-
};
180-
updatedAt: string;
181-
createdAt: string;
182-
_status?: ("draft" | "published") | null;
183-
}
184130
/**
185131
* This interface was referenced by `Config`'s JSON-Schema
186132
* via the `definition` "media".
187133
*/
188134
export interface Media {
189135
id: string;
190136
alt: string;
137+
createdBy?: (string | null) | User;
191138
updatedAt: string;
192139
createdAt: string;
193140
url?: string | null;
@@ -266,6 +213,7 @@ export interface User {
266213
id: string;
267214
firstName: string;
268215
lastName: string;
216+
role: "administrator" | "editor" | "author";
269217
updatedAt: string;
270218
createdAt: string;
271219
email: string;
@@ -277,20 +225,93 @@ export interface User {
277225
lockUntil?: string | null;
278226
password?: string | null;
279227
}
228+
/**
229+
* This interface was referenced by `Config`'s JSON-Schema
230+
* via the `definition` "pages".
231+
*/
232+
export interface Page {
233+
id: string;
234+
title: string;
235+
fullTitle?: string | null;
236+
slug?: string | null;
237+
blocks?:
238+
| {
239+
title: string;
240+
content: {
241+
root: {
242+
type: string;
243+
children: {
244+
type: string;
245+
version: number;
246+
[k: string]: unknown;
247+
}[];
248+
direction: ("ltr" | "rtl") | null;
249+
format:
250+
| "left"
251+
| "start"
252+
| "center"
253+
| "right"
254+
| "end"
255+
| "justify"
256+
| "";
257+
indent: number;
258+
version: number;
259+
};
260+
[k: string]: unknown;
261+
};
262+
id?: string | null;
263+
blockName?: string | null;
264+
blockType: "test";
265+
}[]
266+
| null;
267+
parent?: (string | null) | Page;
268+
breadcrumbs?:
269+
| {
270+
doc?: (string | null) | Page;
271+
url?: string | null;
272+
label?: string | null;
273+
id?: string | null;
274+
}[]
275+
| null;
276+
meta?: {
277+
title?: string | null;
278+
description?: string | null;
279+
};
280+
updatedAt: string;
281+
createdAt: string;
282+
_status?: ("draft" | "published") | null;
283+
}
284+
/**
285+
* This interface was referenced by `Config`'s JSON-Schema
286+
* via the `definition` "posts".
287+
*/
288+
export interface Post {
289+
id: string;
290+
title: string;
291+
slug?: string | null;
292+
createdBy?: (string | null) | User;
293+
updatedAt: string;
294+
createdAt: string;
295+
_status?: ("draft" | "published") | null;
296+
}
280297
/**
281298
* This interface was referenced by `Config`'s JSON-Schema
282299
* via the `definition` "payload-locked-documents".
283300
*/
284301
export interface PayloadLockedDocument {
285302
id: string;
286303
document?:
304+
| ({
305+
relationTo: "media";
306+
value: string | Media;
307+
} | null)
287308
| ({
288309
relationTo: "pages";
289310
value: string | Page;
290311
} | null)
291312
| ({
292-
relationTo: "media";
293-
value: string | Media;
313+
relationTo: "posts";
314+
value: string | Post;
294315
} | null)
295316
| ({
296317
relationTo: "users";
@@ -338,51 +359,13 @@ export interface PayloadMigration {
338359
updatedAt: string;
339360
createdAt: string;
340361
}
341-
/**
342-
* This interface was referenced by `Config`'s JSON-Schema
343-
* via the `definition` "pages_select".
344-
*/
345-
export interface PagesSelect<T extends boolean = true> {
346-
title?: T;
347-
fullTitle?: T;
348-
slug?: T;
349-
blocks?:
350-
| T
351-
| {
352-
test?:
353-
| T
354-
| {
355-
title?: T;
356-
content?: T;
357-
id?: T;
358-
blockName?: T;
359-
};
360-
};
361-
parent?: T;
362-
breadcrumbs?:
363-
| T
364-
| {
365-
doc?: T;
366-
url?: T;
367-
label?: T;
368-
id?: T;
369-
};
370-
meta?:
371-
| T
372-
| {
373-
title?: T;
374-
description?: T;
375-
};
376-
updatedAt?: T;
377-
createdAt?: T;
378-
_status?: T;
379-
}
380362
/**
381363
* This interface was referenced by `Config`'s JSON-Schema
382364
* via the `definition` "media_select".
383365
*/
384366
export interface MediaSelect<T extends boolean = true> {
385367
alt?: T;
368+
createdBy?: T;
386369
updatedAt?: T;
387370
createdAt?: T;
388371
url?: T;
@@ -469,13 +452,65 @@ export interface MediaSelect<T extends boolean = true> {
469452
};
470453
};
471454
}
455+
/**
456+
* This interface was referenced by `Config`'s JSON-Schema
457+
* via the `definition` "pages_select".
458+
*/
459+
export interface PagesSelect<T extends boolean = true> {
460+
title?: T;
461+
fullTitle?: T;
462+
slug?: T;
463+
blocks?:
464+
| T
465+
| {
466+
test?:
467+
| T
468+
| {
469+
title?: T;
470+
content?: T;
471+
id?: T;
472+
blockName?: T;
473+
};
474+
};
475+
parent?: T;
476+
breadcrumbs?:
477+
| T
478+
| {
479+
doc?: T;
480+
url?: T;
481+
label?: T;
482+
id?: T;
483+
};
484+
meta?:
485+
| T
486+
| {
487+
title?: T;
488+
description?: T;
489+
};
490+
updatedAt?: T;
491+
createdAt?: T;
492+
_status?: T;
493+
}
494+
/**
495+
* This interface was referenced by `Config`'s JSON-Schema
496+
* via the `definition` "posts_select".
497+
*/
498+
export interface PostsSelect<T extends boolean = true> {
499+
title?: T;
500+
slug?: T;
501+
createdBy?: T;
502+
updatedAt?: T;
503+
createdAt?: T;
504+
_status?: T;
505+
}
472506
/**
473507
* This interface was referenced by `Config`'s JSON-Schema
474508
* via the `definition` "users_select".
475509
*/
476510
export interface UsersSelect<T extends boolean = true> {
477511
firstName?: T;
478512
lastName?: T;
513+
role?: T;
479514
updatedAt?: T;
480515
createdAt?: T;
481516
email?: T;

apps/trustlab/payload.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import plugins from "@/trustlab/payload/plugins";
1212
import Pages from "@/trustlab/payload/collections/Pages";
1313
import SiteSettings from "@/trustlab/payload/globals";
1414
import { defaultLocale, locales } from "@/trustlab/payload/utils/locales";
15+
import Posts from "@/trustlab/payload/collections/Posts";
1516
const filename = fileURLToPath(import.meta.url);
1617
const dirname = path.dirname(filename);
1718

@@ -57,7 +58,7 @@ export default buildConfig({
5758
globals: ["site-settings"],
5859
},
5960
},
60-
collections: [Pages, Media, Users] as CollectionConfig[],
61+
collections: [Media, Pages, Posts, Users] as CollectionConfig[],
6162
cors,
6263
csrf,
6364
db: mongooseAdapter({

apps/trustlab/src/app/preview/route.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { draftMode } from "next/headers";
55
import { redirect } from "next/navigation";
66

77
import configPromise from "@payload-config";
8+
import { canManageContent } from "@/trustlab/payload/access/abilities";
89

910
export async function GET(
1011
req: {
@@ -31,10 +32,9 @@ export async function GET(
3132
);
3233
}
3334

34-
let user;
35-
35+
let authResult;
3636
try {
37-
user = await payload.auth({
37+
authResult = await payload.auth({
3838
req: req as unknown as PayloadRequest,
3939
headers: req.headers,
4040
});
@@ -43,23 +43,14 @@ export async function GET(
4343
{ err: error },
4444
"Error verifying token for live preview",
4545
);
46-
return new Response("You are not allowed to preview this page", {
47-
status: 403,
48-
});
4946
}
50-
51-
const draft = await draftMode();
52-
53-
if (!user) {
54-
draft.disable();
47+
if (!(authResult && canManageContent(authResult?.user))) {
5548
return new Response("You are not allowed to preview this page", {
5649
status: 403,
5750
});
5851
}
5952

60-
//TODO: Add further checks to ensure the user is allowed to preview this page
61-
53+
const draft = await draftMode();
6254
draft.enable();
63-
6455
redirect(path);
6556
}

0 commit comments

Comments
 (0)