strapi-plugin-oidc 1.6.1 → 1.6.3

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.
package/README.md CHANGED
@@ -119,9 +119,13 @@ Role names are the **display names** shown in **Settings → Roles** (e.g. `"Edi
119
119
  ### Role assignment precedence
120
120
 
121
121
  1. **User's OIDC groups match `OIDC_GROUP_ROLE_MAP`** → use the mapped Strapi roles
122
- 2. **No group match or no mapping configured** → use the default OIDC roles
122
+ 2. **No group match or no mapping configured** → use the default OIDC roles (new users only — see below)
123
123
 
124
- > **Note:** Existing users' roles are updated on every login to reflect current group membership.
124
+ ### Role updates on subsequent logins
125
+
126
+ - **New users** — Roles are always assigned on first login: group-mapped roles if a match is found, otherwise the configured default OIDC roles.
127
+ - **Existing users with a group mapping match** — Roles are updated to reflect the current mapping. If a user's groups change between logins, their Strapi roles are updated accordingly.
128
+ - **Existing users with no group mapping match** — Roles are left unchanged, regardless of what the default OIDC roles are set to. Manually-assigned roles are never overwritten by a default fallback.
125
129
 
126
130
  ## Whitelist API
127
131
 
@@ -476,9 +476,9 @@ function resolveRolesFromGroups(userInfo, config2, availableRoles) {
476
476
  }
477
477
  async function resolveRoles(userInfo, config2, roleService2, availableRoles) {
478
478
  const groupRoles = resolveRolesFromGroups(userInfo, config2, availableRoles);
479
- if (groupRoles.length > 0) return groupRoles;
479
+ if (groupRoles.length > 0) return { roles: groupRoles, fromGroupMapping: true };
480
480
  const oidcRoles = await roleService2.oidcRoles();
481
- return oidcRoles?.roles || [];
481
+ return { roles: oidcRoles?.roles || [], fromGroupMapping: false };
482
482
  }
483
483
  async function registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2) {
484
484
  const defaultLocale = oauthService2.localeFindByHeader(
@@ -494,6 +494,30 @@ async function registerNewUser(oauthService2, email, userResponseData, config2,
494
494
  await oauthService2.triggerWebHook(activateUser);
495
495
  return activateUser;
496
496
  }
497
+ function rolesChanged(current, next) {
498
+ return current.size !== next.size || [...next].some((id) => !current.has(id));
499
+ }
500
+ async function updateUserRoles(user, currentRoleIds, newRoleIds) {
501
+ try {
502
+ strapi.log.info(
503
+ `[OIDC] Roles updated for user ${user.id}: [${[...currentRoleIds].join(",")}] -> [${newRoleIds.join(",")}]`
504
+ );
505
+ await strapi.db.query("admin::user").update({
506
+ where: { id: user.id },
507
+ data: { roles: newRoleIds }
508
+ });
509
+ } catch (updateErr) {
510
+ strapi.log.error({
511
+ code: errorCodes.ROLE_UPDATE_FAILED,
512
+ userId: user.id,
513
+ detail: getErrorDetail("role_update_failed", {
514
+ userId: user.id,
515
+ error: updateErr.message
516
+ })
517
+ });
518
+ throw updateErr;
519
+ }
520
+ }
497
521
  async function handleUserAuthentication(userService, oauthService2, roleService2, whitelistService2, userResponseData, config2, ctx) {
498
522
  const rawEmail = String(userResponseData.email ?? "");
499
523
  const email = rawEmail.toLowerCase();
@@ -502,44 +526,30 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
502
526
  }
503
527
  await whitelistService2.checkWhitelistForEmail(email);
504
528
  const allRoles = await strapi.db.query("admin::role").findMany();
505
- const roles2 = await resolveRoles(userResponseData, config2, roleService2, allRoles);
529
+ const { roles: roles2, fromGroupMapping } = await resolveRoles(
530
+ userResponseData,
531
+ config2,
532
+ roleService2,
533
+ allRoles
534
+ );
506
535
  const resolvedRoleNames = allRoles.filter((r) => roles2.includes(String(r.id))).map((r) => r.name);
507
536
  let userCreated = false;
508
537
  let rolesUpdated = false;
509
- let activateUser = await userService.findOneByEmail(email);
510
- if (!activateUser) {
511
- activateUser = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
538
+ let user = await userService.findOneByEmail(email, ["roles"]);
539
+ if (!user) {
540
+ user = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
512
541
  userCreated = true;
513
- } else if (roles2.length > 0) {
514
- const currentRoleIds = new Set((activateUser.roles ?? []).map((r) => String(r.id)));
515
- const newRoleIds = new Set(roles2);
516
- const rolesChanged = currentRoleIds.size !== newRoleIds.size || [...newRoleIds].some((id) => !currentRoleIds.has(id));
517
- if (rolesChanged) {
518
- try {
519
- strapi.log.info(
520
- `[OIDC] Roles updated for user ${activateUser.id}: [${[...currentRoleIds].join(",")}] -> [${roles2.join(",")}]`
521
- );
522
- await strapi.db.query("admin::user").update({
523
- where: { id: activateUser.id },
524
- data: { roles: roles2 }
525
- });
526
- rolesUpdated = true;
527
- } catch (updateErr) {
528
- strapi.log.error({
529
- code: errorCodes.ROLE_UPDATE_FAILED,
530
- userId: activateUser.id,
531
- detail: getErrorDetail("role_update_failed", {
532
- userId: activateUser.id,
533
- error: updateErr.message
534
- })
535
- });
536
- throw updateErr;
537
- }
542
+ rolesUpdated = true;
543
+ } else if (fromGroupMapping && roles2.length > 0) {
544
+ const currentRoleIds = new Set(user.roles.map((r) => String(r.id)));
545
+ if (rolesChanged(currentRoleIds, new Set(roles2))) {
546
+ await updateUserRoles(user, currentRoleIds, roles2);
547
+ rolesUpdated = true;
538
548
  }
539
549
  }
540
- const jwtToken = await oauthService2.generateToken(activateUser, ctx);
541
- oauthService2.triggerSignInSuccess(activateUser);
542
- return { activateUser, jwtToken, userCreated, rolesUpdated, resolvedRoleNames };
550
+ const jwtToken = await oauthService2.generateToken(user, ctx);
551
+ oauthService2.triggerSignInSuccess(user);
552
+ return { activateUser: user, jwtToken, userCreated, rolesUpdated, resolvedRoleNames };
543
553
  }
544
554
  function classifyOidcError(msg, userInfo) {
545
555
  const errorMap = [
@@ -1525,7 +1535,7 @@ function whitelistService({ strapi: strapi2 }) {
1525
1535
  });
1526
1536
  },
1527
1537
  async removeUser(email) {
1528
- await strapi2.db.query("plugin::strapi-plugin-oidc.whitelists").deleteMany({
1538
+ await getWhitelistQuery().deleteMany({
1529
1539
  where: { email }
1530
1540
  });
1531
1541
  },
@@ -470,9 +470,9 @@ function resolveRolesFromGroups(userInfo, config2, availableRoles) {
470
470
  }
471
471
  async function resolveRoles(userInfo, config2, roleService2, availableRoles) {
472
472
  const groupRoles = resolveRolesFromGroups(userInfo, config2, availableRoles);
473
- if (groupRoles.length > 0) return groupRoles;
473
+ if (groupRoles.length > 0) return { roles: groupRoles, fromGroupMapping: true };
474
474
  const oidcRoles = await roleService2.oidcRoles();
475
- return oidcRoles?.roles || [];
475
+ return { roles: oidcRoles?.roles || [], fromGroupMapping: false };
476
476
  }
477
477
  async function registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2) {
478
478
  const defaultLocale = oauthService2.localeFindByHeader(
@@ -488,6 +488,30 @@ async function registerNewUser(oauthService2, email, userResponseData, config2,
488
488
  await oauthService2.triggerWebHook(activateUser);
489
489
  return activateUser;
490
490
  }
491
+ function rolesChanged(current, next) {
492
+ return current.size !== next.size || [...next].some((id) => !current.has(id));
493
+ }
494
+ async function updateUserRoles(user, currentRoleIds, newRoleIds) {
495
+ try {
496
+ strapi.log.info(
497
+ `[OIDC] Roles updated for user ${user.id}: [${[...currentRoleIds].join(",")}] -> [${newRoleIds.join(",")}]`
498
+ );
499
+ await strapi.db.query("admin::user").update({
500
+ where: { id: user.id },
501
+ data: { roles: newRoleIds }
502
+ });
503
+ } catch (updateErr) {
504
+ strapi.log.error({
505
+ code: errorCodes.ROLE_UPDATE_FAILED,
506
+ userId: user.id,
507
+ detail: getErrorDetail("role_update_failed", {
508
+ userId: user.id,
509
+ error: updateErr.message
510
+ })
511
+ });
512
+ throw updateErr;
513
+ }
514
+ }
491
515
  async function handleUserAuthentication(userService, oauthService2, roleService2, whitelistService2, userResponseData, config2, ctx) {
492
516
  const rawEmail = String(userResponseData.email ?? "");
493
517
  const email = rawEmail.toLowerCase();
@@ -496,44 +520,30 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
496
520
  }
497
521
  await whitelistService2.checkWhitelistForEmail(email);
498
522
  const allRoles = await strapi.db.query("admin::role").findMany();
499
- const roles2 = await resolveRoles(userResponseData, config2, roleService2, allRoles);
523
+ const { roles: roles2, fromGroupMapping } = await resolveRoles(
524
+ userResponseData,
525
+ config2,
526
+ roleService2,
527
+ allRoles
528
+ );
500
529
  const resolvedRoleNames = allRoles.filter((r) => roles2.includes(String(r.id))).map((r) => r.name);
501
530
  let userCreated = false;
502
531
  let rolesUpdated = false;
503
- let activateUser = await userService.findOneByEmail(email);
504
- if (!activateUser) {
505
- activateUser = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
532
+ let user = await userService.findOneByEmail(email, ["roles"]);
533
+ if (!user) {
534
+ user = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
506
535
  userCreated = true;
507
- } else if (roles2.length > 0) {
508
- const currentRoleIds = new Set((activateUser.roles ?? []).map((r) => String(r.id)));
509
- const newRoleIds = new Set(roles2);
510
- const rolesChanged = currentRoleIds.size !== newRoleIds.size || [...newRoleIds].some((id) => !currentRoleIds.has(id));
511
- if (rolesChanged) {
512
- try {
513
- strapi.log.info(
514
- `[OIDC] Roles updated for user ${activateUser.id}: [${[...currentRoleIds].join(",")}] -> [${roles2.join(",")}]`
515
- );
516
- await strapi.db.query("admin::user").update({
517
- where: { id: activateUser.id },
518
- data: { roles: roles2 }
519
- });
520
- rolesUpdated = true;
521
- } catch (updateErr) {
522
- strapi.log.error({
523
- code: errorCodes.ROLE_UPDATE_FAILED,
524
- userId: activateUser.id,
525
- detail: getErrorDetail("role_update_failed", {
526
- userId: activateUser.id,
527
- error: updateErr.message
528
- })
529
- });
530
- throw updateErr;
531
- }
536
+ rolesUpdated = true;
537
+ } else if (fromGroupMapping && roles2.length > 0) {
538
+ const currentRoleIds = new Set(user.roles.map((r) => String(r.id)));
539
+ if (rolesChanged(currentRoleIds, new Set(roles2))) {
540
+ await updateUserRoles(user, currentRoleIds, roles2);
541
+ rolesUpdated = true;
532
542
  }
533
543
  }
534
- const jwtToken = await oauthService2.generateToken(activateUser, ctx);
535
- oauthService2.triggerSignInSuccess(activateUser);
536
- return { activateUser, jwtToken, userCreated, rolesUpdated, resolvedRoleNames };
544
+ const jwtToken = await oauthService2.generateToken(user, ctx);
545
+ oauthService2.triggerSignInSuccess(user);
546
+ return { activateUser: user, jwtToken, userCreated, rolesUpdated, resolvedRoleNames };
537
547
  }
538
548
  function classifyOidcError(msg, userInfo) {
539
549
  const errorMap = [
@@ -1519,7 +1529,7 @@ function whitelistService({ strapi: strapi2 }) {
1519
1529
  });
1520
1530
  },
1521
1531
  async removeUser(email) {
1522
- await strapi2.db.query("plugin::strapi-plugin-oidc.whitelists").deleteMany({
1532
+ await getWhitelistQuery().deleteMany({
1523
1533
  where: { email }
1524
1534
  });
1525
1535
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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",