Skip to content

Commit 98d5555

Browse files
committed
fix: bearer roles
1 parent d7edd40 commit 98d5555

3 files changed

Lines changed: 128 additions & 108 deletions

File tree

packages/auth/docs/payload.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,13 @@ export const Users: CollectionConfig = {
146146
{
147147
name: 'bearer-strategy',
148148
authenticate: async ({ payload, headers }) => {
149-
const result = await authenticateRequestHeaders({headers, payload})
150-
return result as any
149+
const result = await authenticateRequestHeaders({ headers, payload })
150+
if (!result?.user) return { user: null }
151+
const user = await payload.findByID({
152+
collection: 'users',
153+
id: result.user.id,
154+
})
155+
return { user }
151156
},
152157
},
153158
],
@@ -252,6 +257,18 @@ export const GET = auth(async (req) => {
252257
```
253258
254259
260+
261+
#### Example to fetch the logged in userid
262+
263+
```js
264+
import { auth } from "@/lib/auth"
265+
266+
function somefunction() {
267+
const user = await auth().then((sess) => sess?.user?.id); // gets the user id
268+
}
269+
```
270+
271+
255272
#### Environment Variables
256273
```bash
257274
PAYLOAD_SECRET=xxxxx
Lines changed: 103 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22

33
import { verifySession, verifyToken } from "./user";
4+
import { type ValidRole, VALID_ROLES } from "./configuration";
45

56
import { type Payload } from 'payload'
67

@@ -20,6 +21,16 @@ function createAuthError(message: string, statusCode: number): AuthError {
2021

2122
export type { AuthError };
2223

24+
function getValidRole(permissions?: string[]): ValidRole {
25+
if (!permissions || permissions.length === 0) return 'user';
26+
for (const permission of permissions) {
27+
if (VALID_ROLES[permission]) {
28+
return VALID_ROLES[permission];
29+
}
30+
}
31+
return 'user';
32+
}
33+
2334
interface User {
2435
id: string;
2536
email: string;
@@ -69,118 +80,110 @@ export async function authenticateRequest({ req, payload }: AuthenticateRequestO
6980
role: user.role,
7081
method: 'cookie',
7182
}
72-
} else {
73-
const session = await verifySession(req)
74-
75-
if (!session || !session.sub || !session.extra) throw createAuthError("No valid session found", 401);
76-
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID!;
77-
const permissions = ((session.resource_access as Record<string, { roles?: string[] }>)?.[OAUTH_CLIENT_ID]?.roles as string[] | undefined);
78-
if (!permissions) throw createAuthError("User does not have permission to access this application.", 403);
79-
80-
if (!payload) throw createAuthError("Payload instance is required for Keycloak user normalisation", 500);
81-
const payloadUser = (await payload.find({ collection: 'users', depth: 1, limit: 1, draft: false, overrideAccess: true, where: { email: { equals: session.extra.email } } })).docs[0]
82-
83-
if (!payloadUser && session.extra) {
84-
85-
86-
// create the user in Payload
87-
const newUser = await payload.create({
88-
collection: 'users',
89-
data: {
90-
email: session.extra.email,
91-
name: session.extra.name,
92-
role: permissions[0] || 'user',
93-
enabled: true,
94-
accounts: [
95-
{
96-
provider: 'keycloak', providerAccountId: session.sub, type: 'oidc',
97-
}
98-
],
99-
},
100-
draft: false,
101-
overrideAccess: true,
102-
})
103-
console.log("Created new Payload user for Keycloak user:", newUser.id, newUser.email)
104-
return {
105-
106-
method: 'bearer', ...newUser
107-
}
108-
} else if (payloadUser) {
109-
// update user role if changed
110-
if (payloadUser.role !== permissions[0]) {
111-
await payload.update({
112-
collection: 'users',
113-
id: payloadUser.id,
114-
data: {
115-
role: permissions[0] || 'user',
116-
},
117-
draft: false,
118-
overrideAccess: true,
119-
})
120-
console.log(`Updated Payload user role for ${payloadUser.email} to ${permissions}`);
121-
}
122-
123-
return { method: 'bearer', ...payloadUser }
124-
} else {
125-
return null;
126-
}
12783
}
84+
85+
const session = await verifySession(req);
86+
if (!session?.sub || !session.extra) throw createAuthError("No valid session found", 401);
87+
88+
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID!;
89+
const permissions = ((session.resource_access as Record<string, { roles?: string[] }>)?.[OAUTH_CLIENT_ID]?.roles as string[] | undefined);
90+
if (!permissions) throw createAuthError("User does not have permission to access this application.", 403);
91+
92+
if (!payload) throw createAuthError("Payload instance is required for Keycloak user normalisation", 500);
93+
94+
const payloadUser = (await payload.find({
95+
collection: 'users',
96+
depth: 1,
97+
limit: 1,
98+
draft: false,
99+
overrideAccess: true,
100+
where: { email: { equals: session.extra.email } }
101+
})).docs[0];
102+
103+
const role = getValidRole(permissions);
104+
105+
if (!payloadUser) {
106+
const newUser = await payload.create({
107+
collection: 'users',
108+
data: {
109+
email: session.extra.email,
110+
name: session.extra.name,
111+
role,
112+
enabled: true,
113+
accounts: [{ provider: 'keycloak', providerAccountId: session.sub, type: 'oidc' }],
114+
},
115+
draft: false,
116+
overrideAccess: true,
117+
});
118+
console.log("Created new Payload user for Keycloak user:", newUser.id, newUser.email);
119+
return { method: 'bearer', ...newUser };
120+
}
121+
122+
if (payloadUser.role !== role) {
123+
await payload.update({
124+
collection: 'users',
125+
id: payloadUser.id,
126+
data: { role },
127+
draft: false,
128+
overrideAccess: true,
129+
});
130+
console.log(`Updated Payload user role for ${payloadUser.email} to ${role}`);
131+
}
132+
133+
return { method: 'bearer', ...payloadUser };
128134
}
129135

130136
export async function authenticateRequestHeaders({ headers, payload }: { headers: Headers, payload: Payload }) {
131-
if (!headers.get('authorization')) {
132-
return null
133-
}
137+
const authHeader = headers.get('authorization');
138+
if (!authHeader) return null;
134139

135-
const session = await verifyToken(headers.get('authorization')!.replace('Bearer ', ''));
140+
const session = await verifyToken(authHeader.replace('Bearer ', ''));
141+
if (!session?.sub || !session.extra) throw createAuthError("No valid session found", 401);
136142

137-
if (!session || !session.sub || !session.extra) throw createAuthError("No valid session found", 401);
138143
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENT_ID!;
139144
const permissions = ((session.resource_access as Record<string, { roles?: string[] }>)?.[OAUTH_CLIENT_ID]?.roles as string[] | undefined);
140145
if (!permissions) throw createAuthError("User does not have permission to access this application.", 403);
141146

142147
if (!payload) throw createAuthError("Payload instance is required for Keycloak user normalisation", 500);
143-
const payloadUser = (await payload.find({ collection: 'users', depth: 1, limit: 1, draft: false, overrideAccess: true, where: { email: { equals: session.extra.email } } })).docs[0]
144-
145-
if (!payloadUser && session.extra) {
146-
// create the user in Payload
147-
const newUser = await payload.create({
148-
collection: 'users',
149-
data: {
150-
email: session.extra.email,
151-
name: session.extra.name,
152-
role: permissions[0] || 'user',
153-
enabled: true,
154-
accounts: [
155-
{
156-
provider: 'keycloak', providerAccountId: session.sub, type: 'oidc',
157-
}
158-
],
159-
},
160-
draft: false,
161-
overrideAccess: true,
162-
})
163-
console.log("Created new Payload user for Keycloak user:", newUser.id, newUser.email)
164-
return { user: { ...newUser, collection: 'users' as const } }
165-
166-
} else if (payloadUser) {
167-
// update user role if changed
168-
if (payloadUser.role !== permissions[0]) {
169-
await payload.update({
170-
collection: 'users',
171-
id: payloadUser.id,
172-
data: {
173-
role: permissions[0] || 'user',
174-
},
175-
draft: false,
176-
overrideAccess: true,
177-
})
178-
console.log(`Updated Payload user role for ${payloadUser.email} to ${permissions}`);
179-
}
180148

181-
return { user: { ...payloadUser, collection: 'users' as const } }
149+
const payloadUser = (await payload.find({
150+
collection: 'users',
151+
depth: 1,
152+
limit: 1,
153+
draft: false,
154+
overrideAccess: true,
155+
where: { email: { equals: session.extra.email } }
156+
})).docs[0];
157+
158+
const role = getValidRole(permissions);
159+
160+
if (!payloadUser) {
161+
const newUser = await payload.create({
162+
collection: 'users',
163+
data: {
164+
email: session.extra.email,
165+
name: session.extra.name,
166+
role,
167+
enabled: true,
168+
accounts: [{ provider: 'keycloak', providerAccountId: session.sub, type: 'oidc' }],
169+
},
170+
draft: false,
171+
overrideAccess: true,
172+
});
173+
console.log("Created new Payload user for Keycloak user:", newUser.id, newUser.email);
174+
return { user: { ...newUser, collection: 'users' as const } };
175+
}
182176

183-
} else {
184-
return null;
177+
if (payloadUser.role !== role) {
178+
await payload.update({
179+
collection: 'users',
180+
id: payloadUser.id,
181+
data: { role },
182+
draft: false,
183+
overrideAccess: true,
184+
});
185+
console.log(`Updated Payload user role for ${payloadUser.email} to ${role}`);
185186
}
187+
188+
return { user: { ...payloadUser, collection: 'users' as const } };
186189
}

packages/auth/src/payload-jwt/configuration.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function upsertAccount(existing: AccountType[] = [], account: IncomingAccount, u
5959
}
6060

6161

62-
type ValidRole = 'user' | 'admin' | 'digital-colleague'
63-
const validRoles: Record<string, ValidRole> = {
62+
export type ValidRole = 'user' | 'admin' | 'digital-colleague'
63+
export const VALID_ROLES: Record<string, ValidRole> = {
6464
'admin': 'admin',
6565
'digital-colleague': 'digital-colleague',
6666
'user': 'user',
@@ -73,8 +73,8 @@ function profileRoles(profile: { sub: string;[key: string]: unknown }, tokens: {
7373
const permissions = ((decodedJWT.resource_access as Record<string, { roles?: string[] }>)?.[process.env.OAUTH_CLIENT_ID!]?.roles as string[] | undefined);
7474
if (permissions && Array.isArray(permissions)) {
7575
for (const permission of permissions) {
76-
if (validRoles[permission]) {
77-
role = validRoles[permission];
76+
if (VALID_ROLES[permission]) {
77+
role = VALID_ROLES[permission];
7878
break;
7979
}
8080
}
@@ -101,8 +101,8 @@ async function persistTokens(userId: string, account: IncomingAccount, payloadCo
101101
const permissions = ((decodedJWT.resource_access as Record<string, { roles?: string[] }>)?.[process.env.OAUTH_CLIENT_ID!]?.roles as string[] | undefined);
102102
if (permissions && Array.isArray(permissions)) {
103103
for (const permission of permissions) {
104-
if (validRoles[permission]) {
105-
role = validRoles[permission];
104+
if (VALID_ROLES[permission]) {
105+
role = VALID_ROLES[permission];
106106
break;
107107
}
108108
}

0 commit comments

Comments
 (0)