strapi-plugin-oidc 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -118,10 +118,22 @@ const contentTypes = {
118
118
  };
119
119
  function configValidation() {
120
120
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
121
- if (config2["OIDC_CLIENT_ID"] && config2["OIDC_CLIENT_SECRET"] && config2["OIDC_REDIRECT_URI"] && config2["OIDC_SCOPES"] && config2["OIDC_TOKEN_ENDPOINT"] && config2["OIDC_USER_INFO_ENDPOINT"] && config2["OIDC_GRANT_TYPE"] && config2["OIDC_FAMILY_NAME_FIELD"] && config2["OIDC_GIVEN_NAME_FIELD"] && config2["OIDC_AUTHORIZATION_ENDPOINT"]) {
121
+ const requiredKeys = [
122
+ "OIDC_CLIENT_ID",
123
+ "OIDC_CLIENT_SECRET",
124
+ "OIDC_REDIRECT_URI",
125
+ "OIDC_SCOPES",
126
+ "OIDC_TOKEN_ENDPOINT",
127
+ "OIDC_USER_INFO_ENDPOINT",
128
+ "OIDC_GRANT_TYPE",
129
+ "OIDC_FAMILY_NAME_FIELD",
130
+ "OIDC_GIVEN_NAME_FIELD",
131
+ "OIDC_AUTHORIZATION_ENDPOINT"
132
+ ];
133
+ if (requiredKeys.every((key) => config2[key])) {
122
134
  return config2;
123
135
  }
124
- throw new Error("OIDC_AUTHORIZATION_ENDPOINT,OIDC_TOKEN_ENDPOINT, OIDC_USER_INFO_ENDPOINT,OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI, and OIDC_SCOPES are required");
136
+ throw new Error(`The following configuration keys are required: ${requiredKeys.join(", ")}`);
125
137
  }
126
138
  async function oidcSignIn(ctx) {
127
139
  let { state } = ctx.query;
@@ -144,6 +156,66 @@ async function oidcSignIn(ctx) {
144
156
  ctx.set("Location", authorizationUrl);
145
157
  return ctx.send({}, 302);
146
158
  }
159
+ async function exchangeTokenAndFetchUserInfo(httpClient, config2, params) {
160
+ const response = await httpClient.post(config2.OIDC_TOKEN_ENDPOINT, params, {
161
+ headers: {
162
+ "Content-Type": "application/x-www-form-urlencoded"
163
+ }
164
+ });
165
+ let userInfoEndpointHeaders = {};
166
+ let userInfoEndpointParameters = `?access_token=${response.data.access_token}`;
167
+ if (config2.OIDC_USER_INFO_ENDPOINT_WITH_AUTH_HEADER) {
168
+ userInfoEndpointHeaders = {
169
+ headers: { Authorization: `Bearer ${response.data.access_token}` }
170
+ };
171
+ userInfoEndpointParameters = "";
172
+ }
173
+ const userInfoEndpoint = `${config2.OIDC_USER_INFO_ENDPOINT}${userInfoEndpointParameters}`;
174
+ const userResponse = await httpClient.get(userInfoEndpoint, userInfoEndpointHeaders);
175
+ return userResponse.data;
176
+ }
177
+ async function registerNewUser(userService, oauthService2, roleService2, email, userResponseData, whitelistUser, config2, ctx) {
178
+ let roles2 = [];
179
+ if (whitelistUser?.roles?.length > 0) {
180
+ roles2 = whitelistUser.roles;
181
+ } else {
182
+ const oidcRoles = await roleService2.oidcRoles();
183
+ roles2 = oidcRoles?.roles || [];
184
+ }
185
+ const defaultLocale = oauthService2.localeFindByHeader(ctx.request.headers);
186
+ const activateUser = await oauthService2.createUser(
187
+ email,
188
+ userResponseData[config2.OIDC_FAMILY_NAME_FIELD],
189
+ userResponseData[config2.OIDC_GIVEN_NAME_FIELD],
190
+ defaultLocale,
191
+ roles2
192
+ );
193
+ await oauthService2.triggerWebHook(activateUser);
194
+ return activateUser;
195
+ }
196
+ async function handleUserAuthentication(userService, oauthService2, roleService2, whitelistService2, userResponseData, config2, ctx) {
197
+ const email = userResponseData.email;
198
+ const whitelistUser = await whitelistService2.checkWhitelistForEmail(email);
199
+ const dbUser = await userService.findOneByEmail(email);
200
+ let activateUser;
201
+ if (dbUser) {
202
+ activateUser = dbUser;
203
+ } else {
204
+ activateUser = await registerNewUser(
205
+ userService,
206
+ oauthService2,
207
+ roleService2,
208
+ email,
209
+ userResponseData,
210
+ whitelistUser,
211
+ config2,
212
+ ctx
213
+ );
214
+ }
215
+ const jwtToken = await oauthService2.generateToken(activateUser, ctx);
216
+ oauthService2.triggerSignInSuccess(activateUser);
217
+ return { activateUser, jwtToken };
218
+ }
147
219
  async function oidcSignInCallback(ctx) {
148
220
  const config2 = configValidation();
149
221
  const httpClient = axios.create();
@@ -152,77 +224,41 @@ async function oidcSignInCallback(ctx) {
152
224
  const roleService2 = strapi.plugin("strapi-plugin-oidc").service("role");
153
225
  const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
154
226
  if (!ctx.query.code) {
155
- return ctx.send(oauthService2.renderSignUpError(`code Not Found`));
227
+ return ctx.send(oauthService2.renderSignUpError("code Not Found"));
156
228
  }
157
229
  if (!ctx.query.state || ctx.query.state !== ctx.session.oidcState) {
158
- return ctx.send(oauthService2.renderSignUpError(`Invalid state`));
230
+ return ctx.send(oauthService2.renderSignUpError("Invalid state"));
159
231
  }
160
232
  const params = new URLSearchParams();
161
233
  params.append("code", ctx.query.code);
162
- params.append("client_id", config2["OIDC_CLIENT_ID"]);
163
- params.append("client_secret", config2["OIDC_CLIENT_SECRET"]);
164
- params.append("redirect_uri", config2["OIDC_REDIRECT_URI"]);
165
- params.append("grant_type", config2["OIDC_GRANT_TYPE"]);
234
+ params.append("client_id", config2.OIDC_CLIENT_ID);
235
+ params.append("client_secret", config2.OIDC_CLIENT_SECRET);
236
+ params.append("redirect_uri", config2.OIDC_REDIRECT_URI);
237
+ params.append("grant_type", config2.OIDC_GRANT_TYPE);
166
238
  params.append("code_verifier", ctx.session.codeVerifier);
167
239
  try {
168
- const response = await httpClient.post(config2["OIDC_TOKEN_ENDPOINT"], params, {
169
- headers: {
170
- "Content-Type": "application/x-www-form-urlencoded"
171
- }
172
- });
173
- let userInfoEndpointHeaders = {};
174
- let userInfoEndpointParameters = `?access_token=${response.data.access_token}`;
175
- if (config2["OIDC_USER_INFO_ENDPOINT_WITH_AUTH_HEADER"]) {
176
- userInfoEndpointHeaders = {
177
- headers: { Authorization: `Bearer ${response.data.access_token}` }
178
- };
179
- userInfoEndpointParameters = "";
180
- }
181
- const userInfoEndpoint = `${config2["OIDC_USER_INFO_ENDPOINT"]}${userInfoEndpointParameters}`;
182
- const userResponse = await httpClient.get(
183
- userInfoEndpoint,
184
- userInfoEndpointHeaders
240
+ const userResponseData = await exchangeTokenAndFetchUserInfo(httpClient, config2, params);
241
+ const { activateUser, jwtToken } = await handleUserAuthentication(
242
+ userService,
243
+ oauthService2,
244
+ roleService2,
245
+ whitelistService2,
246
+ userResponseData,
247
+ config2,
248
+ ctx
185
249
  );
186
- const email = userResponse.data.email;
187
- const whitelistUser = await whitelistService2.checkWhitelistForEmail(email);
188
- const dbUser = await userService.findOneByEmail(email);
189
- let activateUser;
190
- let jwtToken;
191
- if (dbUser) {
192
- activateUser = dbUser;
193
- jwtToken = await oauthService2.generateToken(dbUser, ctx);
194
- } else {
195
- let roles2 = [];
196
- if (whitelistUser && whitelistUser.roles && whitelistUser.roles.length > 0) {
197
- roles2 = whitelistUser.roles;
198
- } else {
199
- const oidcRoles = await roleService2.oidcRoles();
200
- roles2 = oidcRoles && oidcRoles["roles"] ? oidcRoles["roles"] : [];
201
- }
202
- const defaultLocale = oauthService2.localeFindByHeader(ctx.request.headers);
203
- activateUser = await oauthService2.createUser(
204
- email,
205
- userResponse.data[config2["OIDC_FAMILY_NAME_FIELD"]],
206
- userResponse.data[config2["OIDC_GIVEN_NAME_FIELD"]],
207
- defaultLocale,
208
- roles2
209
- );
210
- jwtToken = await oauthService2.generateToken(activateUser, ctx);
211
- await oauthService2.triggerWebHook(activateUser);
212
- }
213
- oauthService2.triggerSignInSuccess(activateUser);
214
250
  const nonce = randomUUID();
215
251
  const html = oauthService2.renderSignUpSuccess(jwtToken, activateUser, nonce);
216
252
  ctx.set("Content-Security-Policy", `script-src 'nonce-${nonce}'`);
217
253
  ctx.send(html);
218
254
  } catch (e) {
219
- console.error(e);
255
+ console.error("ERROR CAUGHT IN OIDC SIGNIN:", e);
220
256
  ctx.send(oauthService2.renderSignUpError(e.message));
221
257
  }
222
258
  }
223
259
  async function logout(ctx) {
224
260
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
225
- const logoutUrl = config2["OIDC_LOGOUT_URL"];
261
+ const logoutUrl = config2.OIDC_LOGOUT_URL;
226
262
  if (logoutUrl) {
227
263
  ctx.redirect(logoutUrl);
228
264
  } else {
@@ -240,10 +276,9 @@ async function find(ctx) {
240
276
  const roles2 = await roleService2.find();
241
277
  const oidcConstants = roleService2.getOidcRoles();
242
278
  for (const oidc2 of oidcConstants) {
243
- for (const role2 of roles2) {
244
- if (role2["oauth_type"] === oidc2["oauth_type"]) {
245
- oidc2["role"] = role2["roles"];
246
- }
279
+ const matchedRole = roles2.find((r) => r.oauth_type === oidc2.oauth_type);
280
+ if (matchedRole) {
281
+ oidc2.role = matchedRole.roles;
247
282
  }
248
283
  }
249
284
  ctx.send(oidcConstants);
@@ -255,7 +290,7 @@ async function update(ctx) {
255
290
  await roleService2.update(roles2);
256
291
  ctx.send({}, 204);
257
292
  } catch (e) {
258
- console.log(e);
293
+ console.error(e);
259
294
  ctx.send({}, 400);
260
295
  }
261
296
  }
@@ -289,12 +324,10 @@ async function publicSettings(ctx) {
289
324
  async function register(ctx) {
290
325
  const { email, roles: roles2 } = ctx.request.body;
291
326
  if (!email) {
292
- ctx.body = {
293
- message: "Please enter a valid email address"
294
- };
327
+ ctx.body = { message: "Please enter a valid email address" };
295
328
  return;
296
329
  }
297
- const emailList = Array.isArray(email) ? email : email.split(",").map((e) => e.trim()).filter((e) => e);
330
+ const emailList = Array.isArray(email) ? email : email.split(",").map((e) => e.trim()).filter(Boolean);
298
331
  const existingUsers = await strapi.query("admin::user").findMany({
299
332
  where: { email: { $in: emailList } },
300
333
  populate: ["roles"]
@@ -304,8 +337,8 @@ async function register(ctx) {
304
337
  for (const singleEmail of emailList) {
305
338
  const existingUser = existingUsers.find((u) => u.email === singleEmail);
306
339
  let finalRoles = roles2;
307
- if (existingUser && existingUser.roles) {
308
- finalRoles = existingUser.roles.map((r) => r.id.toString());
340
+ if (existingUser?.roles) {
341
+ finalRoles = existingUser.roles.map((r) => String(r.id));
309
342
  matchedExistingUsersCount++;
310
343
  }
311
344
  const alreadyWhitelisted = await strapi.query("plugin::strapi-plugin-oidc.whitelists").findOne({
@@ -342,8 +375,8 @@ async function syncUsers(ctx) {
342
375
  const existingStrapiUser = existingStrapiUsers.find((u) => u.email === user.email);
343
376
  let finalRoles = user.roles;
344
377
  const currUser = currentUsers.find((u) => u.email === user.email);
345
- if (!currUser && existingStrapiUser && existingStrapiUser.roles) {
346
- finalRoles = existingStrapiUser.roles.map((r) => r.id.toString());
378
+ if (!currUser && existingStrapiUser?.roles) {
379
+ finalRoles = existingStrapiUser.roles.map((r) => String(r.id));
347
380
  matchedExistingUsersCount++;
348
381
  }
349
382
  if (currUser) {
@@ -493,8 +526,8 @@ function oauthService({ strapi: strapi2 }) {
493
526
  }
494
527
  }
495
528
  const createdUser = await userService.create({
496
- firstname: firstname ? firstname : "unset",
497
- lastname: lastname ? lastname : "",
529
+ firstname: firstname || "unset",
530
+ lastname: lastname || "",
498
531
  email: email.toLocaleLowerCase(),
499
532
  roles: roles2,
500
533
  preferedLanguage: locale
@@ -502,8 +535,8 @@ function oauthService({ strapi: strapi2 }) {
502
535
  return await userService.register({
503
536
  registrationToken: createdUser.registrationToken,
504
537
  userInfo: {
505
- firstname: firstname ? firstname : "unset",
506
- lastname: lastname ? lastname : "user",
538
+ firstname: firstname || "unset",
539
+ lastname: lastname || "user",
507
540
  password: generator.generate({
508
541
  length: 43,
509
542
  // 256 bits (https://en.wikipedia.org/wiki/Password_strength#Random_passwords)
@@ -527,11 +560,7 @@ function oauthService({ strapi: strapi2 }) {
527
560
  return `${origin}+${alias}${domain}`;
528
561
  },
529
562
  localeFindByHeader(headers) {
530
- if (headers["accept-language"] && headers["accept-language"].includes("ja")) {
531
- return "ja";
532
- } else {
533
- return "en";
534
- }
563
+ return headers["accept-language"]?.includes("ja") ? "ja" : "en";
535
564
  },
536
565
  async triggerWebHook(user) {
537
566
  let ENTRY_CREATE;
@@ -554,7 +583,7 @@ function oauthService({ strapi: strapi2 }) {
554
583
  });
555
584
  },
556
585
  triggerSignInSuccess(user) {
557
- delete user["password"];
586
+ delete user.password;
558
587
  const eventHub = strapi2.serviceMap.get("eventHub");
559
588
  eventHub.emit("admin.auth.success", {
560
589
  user,
@@ -654,35 +683,35 @@ function roleService({ strapi: strapi2 }) {
654
683
  getOidcRoles() {
655
684
  return [
656
685
  {
657
- "oauth_type": this.OIDC_TYPE,
686
+ oauth_type: this.OIDC_TYPE,
658
687
  name: "OIDC"
659
688
  }
660
689
  ];
661
690
  },
662
691
  async oidcRoles() {
663
- return await strapi2.query("plugin::strapi-plugin-oidc.roles").findOne({
692
+ return strapi2.query("plugin::strapi-plugin-oidc.roles").findOne({
664
693
  where: {
665
- "oauth_type": this.OIDC_TYPE
694
+ oauth_type: this.OIDC_TYPE
666
695
  }
667
696
  });
668
697
  },
669
698
  async find() {
670
- return await strapi2.query("plugin::strapi-plugin-oidc.roles").findMany();
699
+ return strapi2.query("plugin::strapi-plugin-oidc.roles").findMany();
671
700
  },
672
701
  async update(roles2) {
673
702
  const query = strapi2.query("plugin::strapi-plugin-oidc.roles");
674
703
  await Promise.all(
675
704
  roles2.map(async (role2) => {
676
- const oidcRole = await query.findOne({ where: { "oauth_type": role2["oauth_type"] } });
705
+ const oidcRole = await query.findOne({ where: { oauth_type: role2.oauth_type } });
677
706
  if (oidcRole) {
678
707
  await query.update({
679
- where: { "oauth_type": role2["oauth_type"] },
708
+ where: { oauth_type: role2.oauth_type },
680
709
  data: { roles: role2.role }
681
710
  });
682
711
  } else {
683
712
  await query.create({
684
713
  data: {
685
- "oauth_type": role2["oauth_type"],
714
+ oauth_type: role2.oauth_type,
686
715
  roles: role2.role
687
716
  }
688
717
  });
@@ -709,7 +738,7 @@ function whitelistService({ strapi: strapi2 }) {
709
738
  },
710
739
  async getUsers() {
711
740
  const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
712
- return await query.findMany();
741
+ return query.findMany();
713
742
  },
714
743
  async registerUser(email, roles2) {
715
744
  const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
@@ -730,6 +759,7 @@ function whitelistService({ strapi: strapi2 }) {
730
759
  },
731
760
  async checkWhitelistForEmail(email) {
732
761
  const settings = await this.getSettings();
762
+ console.log("checkWhitelistForEmail settings:", settings);
733
763
  if (!settings.useWhitelist) {
734
764
  return null;
735
765
  }
@@ -739,7 +769,8 @@ function whitelistService({ strapi: strapi2 }) {
739
769
  email
740
770
  }
741
771
  });
742
- if (result === null) {
772
+ console.log("checkWhitelistForEmail result:", result);
773
+ if (!result) {
743
774
  throw new Error("Not present in whitelist");
744
775
  }
745
776
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A Strapi plugin that provides OpenID Connect (OIDC) authentication functionality for the Strapi Admin Panel.",
5
5
  "strapi": {
6
6
  "displayName": "OIDC Plugin",
@@ -15,6 +15,8 @@
15
15
  "verify": "strapi-plugin verify",
16
16
  "test": "vitest run -c vitest.config.e2e.ts",
17
17
  "lint": "eslint",
18
+ "fallow:check": "fallow",
19
+ "fallow:fix": "fallow fix --yes",
18
20
  "prepare": "husky"
19
21
  },
20
22
  "lint-staged": {
@@ -73,7 +75,6 @@
73
75
  "@eslint/eslintrc": "^3.3.5",
74
76
  "@eslint/js": "^10.0.1",
75
77
  "@strapi/sdk-plugin": "^6.0.1",
76
- "@strapi/typescript-utils": "^5.41.1",
77
78
  "@types/node": "^25.5.2",
78
79
  "@types/supertest": "^7.2.0",
79
80
  "@vitest/coverage-v8": "^4.1.2",