strapi-plugin-oidc 1.6.0 → 1.6.2
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 +10 -9
- package/dist/server/index.js +45 -35
- package/dist/server/index.mjs +45 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,12 @@ Role names are the **display names** shown in **Settings → Roles** (e.g. `"Edi
|
|
|
121
121
|
1. **User's OIDC groups match `OIDC_GROUP_ROLE_MAP`** → use the mapped Strapi roles
|
|
122
122
|
2. **No group match or no mapping configured** → use the default OIDC roles
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
### Role updates on subsequent logins
|
|
125
|
+
|
|
126
|
+
- **New users** — OIDC roles are always assigned on first login.
|
|
127
|
+
- **Existing users with manually unchanged roles** — If a user's current roles still match the roles assigned by OIDC on their previous login (i.e., an administrator has not manually changed their roles), their roles are updated to reflect the current group mapping. This ensures that when the group-to-role mapping changes, returning users pick up the new roles automatically.
|
|
128
|
+
- **Existing users with manually changed roles** — If an administrator has manually assigned the user different roles since their last OIDC login, the user's roles are left unchanged. OIDC will not overwrite a manual role assignment.
|
|
129
|
+
- **Mapping removed or user's groups don't map** — If the `OIDC_GROUP_ROLE_MAP` is removed, a user's groups no longer match any mapping, or there are no default OIDC roles configured, the user keeps their last known roles.
|
|
125
130
|
|
|
126
131
|
## Whitelist API
|
|
127
132
|
|
|
@@ -140,14 +145,10 @@ API calls write directly to the database — there is no unsaved state.
|
|
|
140
145
|
|
|
141
146
|
### Import format
|
|
142
147
|
|
|
143
|
-
Accepted by both the API import endpoint and the Admin UI import button.
|
|
148
|
+
Accepted by both the API import endpoint and the Admin UI import button. If the email already exists as a Strapi admin user, their current roles are used automatically.
|
|
144
149
|
|
|
145
150
|
```json
|
|
146
|
-
[
|
|
147
|
-
{ "email": "alice@example.com", "roles": ["Editor"] },
|
|
148
|
-
{ "email": "bob@example.com", "roles": ["Editor", "Author"] },
|
|
149
|
-
{ "email": "carol@example.com" }
|
|
150
|
-
]
|
|
151
|
+
[{ "email": "alice@example.com" }, { "email": "bob@example.com" }]
|
|
151
152
|
```
|
|
152
153
|
|
|
153
154
|
Duplicate emails within the payload and emails already in the whitelist are silently skipped.
|
|
@@ -166,12 +167,12 @@ curl -H "Authorization: Bearer <token>" \
|
|
|
166
167
|
|
|
167
168
|
# Add
|
|
168
169
|
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
|
169
|
-
-d '{"email": "user@example.com"
|
|
170
|
+
-d '{"email": "user@example.com"}' \
|
|
170
171
|
http://localhost:1337/api/strapi-plugin-oidc/whitelist
|
|
171
172
|
|
|
172
173
|
# Bulk import
|
|
173
174
|
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
|
174
|
-
-d '{"users": [{"email": "a@example.com"
|
|
175
|
+
-d '{"users": [{"email": "a@example.com"}, {"email": "b@example.com"}]}' \
|
|
175
176
|
http://localhost:1337/api/strapi-plugin-oidc/whitelist/import
|
|
176
177
|
|
|
177
178
|
# Delete one (by email)
|
package/dist/server/index.js
CHANGED
|
@@ -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();
|
|
@@ -506,40 +530,26 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
|
|
|
506
530
|
const resolvedRoleNames = allRoles.filter((r) => roles2.includes(String(r.id))).map((r) => r.name);
|
|
507
531
|
let userCreated = false;
|
|
508
532
|
let rolesUpdated = false;
|
|
509
|
-
let
|
|
510
|
-
if (!
|
|
511
|
-
|
|
533
|
+
let user = await userService.findOneByEmail(email, ["roles"]);
|
|
534
|
+
if (!user) {
|
|
535
|
+
user = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
|
|
512
536
|
userCreated = true;
|
|
537
|
+
rolesUpdated = true;
|
|
513
538
|
} else if (roles2.length > 0) {
|
|
514
|
-
const
|
|
539
|
+
const defaultRoleIds = new Set(user.roles.map((r) => String(r.id)));
|
|
540
|
+
const currentRoleIds = new Set(user.roles.map((r) => String(r.id)));
|
|
515
541
|
const newRoleIds = new Set(roles2);
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
});
|
|
542
|
+
if (rolesChanged(currentRoleIds, newRoleIds)) {
|
|
543
|
+
const isOnDefaultRoles = currentRoleIds.size === defaultRoleIds.size && [...currentRoleIds].every((id) => defaultRoleIds.has(id));
|
|
544
|
+
if (isOnDefaultRoles) {
|
|
545
|
+
await updateUserRoles(user, currentRoleIds, roles2);
|
|
526
546
|
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
547
|
}
|
|
538
548
|
}
|
|
539
549
|
}
|
|
540
|
-
const jwtToken = await oauthService2.generateToken(
|
|
541
|
-
oauthService2.triggerSignInSuccess(
|
|
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 = [
|
|
@@ -834,9 +844,9 @@ async function register(ctx) {
|
|
|
834
844
|
ctx.body = { matchedExistingUsersCount };
|
|
835
845
|
}
|
|
836
846
|
async function removeEmail(ctx) {
|
|
837
|
-
const {
|
|
847
|
+
const { email } = ctx.params;
|
|
838
848
|
const whitelistService2 = getWhitelistService();
|
|
839
|
-
await whitelistService2.removeUser(
|
|
849
|
+
await whitelistService2.removeUser(email);
|
|
840
850
|
ctx.body = {};
|
|
841
851
|
}
|
|
842
852
|
async function deleteAll(ctx) {
|
|
@@ -880,7 +890,7 @@ async function syncUsers(ctx) {
|
|
|
880
890
|
const currentUsersByEmail = new Map(currentUsers.map((u) => [u.email, u]));
|
|
881
891
|
for (const currUser of currentUsers) {
|
|
882
892
|
if (!syncEmailSet.has(currUser.email)) {
|
|
883
|
-
await whitelistService2.removeUser(currUser.
|
|
893
|
+
await whitelistService2.removeUser(currUser.email);
|
|
884
894
|
}
|
|
885
895
|
}
|
|
886
896
|
for (const email of emails) {
|
|
@@ -1063,7 +1073,7 @@ const routes = {
|
|
|
1063
1073
|
},
|
|
1064
1074
|
{
|
|
1065
1075
|
method: "DELETE",
|
|
1066
|
-
path: "/whitelist/:
|
|
1076
|
+
path: "/whitelist/:email",
|
|
1067
1077
|
handler: "whitelist.removeEmail",
|
|
1068
1078
|
config: adminPolicies("update")
|
|
1069
1079
|
},
|
|
@@ -1122,7 +1132,7 @@ const routes = {
|
|
|
1122
1132
|
},
|
|
1123
1133
|
{
|
|
1124
1134
|
method: "DELETE",
|
|
1125
|
-
path: "/whitelist/:
|
|
1135
|
+
path: "/whitelist/:email",
|
|
1126
1136
|
handler: "whitelist.removeEmail"
|
|
1127
1137
|
},
|
|
1128
1138
|
{
|
|
@@ -1524,9 +1534,9 @@ function whitelistService({ strapi: strapi2 }) {
|
|
|
1524
1534
|
data: { email }
|
|
1525
1535
|
});
|
|
1526
1536
|
},
|
|
1527
|
-
async removeUser(
|
|
1528
|
-
await getWhitelistQuery().
|
|
1529
|
-
where: {
|
|
1537
|
+
async removeUser(email) {
|
|
1538
|
+
await getWhitelistQuery().deleteMany({
|
|
1539
|
+
where: { email }
|
|
1530
1540
|
});
|
|
1531
1541
|
},
|
|
1532
1542
|
async checkWhitelistForEmail(email) {
|
package/dist/server/index.mjs
CHANGED
|
@@ -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();
|
|
@@ -500,40 +524,26 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
|
|
|
500
524
|
const resolvedRoleNames = allRoles.filter((r) => roles2.includes(String(r.id))).map((r) => r.name);
|
|
501
525
|
let userCreated = false;
|
|
502
526
|
let rolesUpdated = false;
|
|
503
|
-
let
|
|
504
|
-
if (!
|
|
505
|
-
|
|
527
|
+
let user = await userService.findOneByEmail(email, ["roles"]);
|
|
528
|
+
if (!user) {
|
|
529
|
+
user = await registerNewUser(oauthService2, email, userResponseData, config2, ctx, roles2);
|
|
506
530
|
userCreated = true;
|
|
531
|
+
rolesUpdated = true;
|
|
507
532
|
} else if (roles2.length > 0) {
|
|
508
|
-
const
|
|
533
|
+
const defaultRoleIds = new Set(user.roles.map((r) => String(r.id)));
|
|
534
|
+
const currentRoleIds = new Set(user.roles.map((r) => String(r.id)));
|
|
509
535
|
const newRoleIds = new Set(roles2);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
});
|
|
536
|
+
if (rolesChanged(currentRoleIds, newRoleIds)) {
|
|
537
|
+
const isOnDefaultRoles = currentRoleIds.size === defaultRoleIds.size && [...currentRoleIds].every((id) => defaultRoleIds.has(id));
|
|
538
|
+
if (isOnDefaultRoles) {
|
|
539
|
+
await updateUserRoles(user, currentRoleIds, roles2);
|
|
520
540
|
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
541
|
}
|
|
532
542
|
}
|
|
533
543
|
}
|
|
534
|
-
const jwtToken = await oauthService2.generateToken(
|
|
535
|
-
oauthService2.triggerSignInSuccess(
|
|
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 = [
|
|
@@ -828,9 +838,9 @@ async function register(ctx) {
|
|
|
828
838
|
ctx.body = { matchedExistingUsersCount };
|
|
829
839
|
}
|
|
830
840
|
async function removeEmail(ctx) {
|
|
831
|
-
const {
|
|
841
|
+
const { email } = ctx.params;
|
|
832
842
|
const whitelistService2 = getWhitelistService();
|
|
833
|
-
await whitelistService2.removeUser(
|
|
843
|
+
await whitelistService2.removeUser(email);
|
|
834
844
|
ctx.body = {};
|
|
835
845
|
}
|
|
836
846
|
async function deleteAll(ctx) {
|
|
@@ -874,7 +884,7 @@ async function syncUsers(ctx) {
|
|
|
874
884
|
const currentUsersByEmail = new Map(currentUsers.map((u) => [u.email, u]));
|
|
875
885
|
for (const currUser of currentUsers) {
|
|
876
886
|
if (!syncEmailSet.has(currUser.email)) {
|
|
877
|
-
await whitelistService2.removeUser(currUser.
|
|
887
|
+
await whitelistService2.removeUser(currUser.email);
|
|
878
888
|
}
|
|
879
889
|
}
|
|
880
890
|
for (const email of emails) {
|
|
@@ -1057,7 +1067,7 @@ const routes = {
|
|
|
1057
1067
|
},
|
|
1058
1068
|
{
|
|
1059
1069
|
method: "DELETE",
|
|
1060
|
-
path: "/whitelist/:
|
|
1070
|
+
path: "/whitelist/:email",
|
|
1061
1071
|
handler: "whitelist.removeEmail",
|
|
1062
1072
|
config: adminPolicies("update")
|
|
1063
1073
|
},
|
|
@@ -1116,7 +1126,7 @@ const routes = {
|
|
|
1116
1126
|
},
|
|
1117
1127
|
{
|
|
1118
1128
|
method: "DELETE",
|
|
1119
|
-
path: "/whitelist/:
|
|
1129
|
+
path: "/whitelist/:email",
|
|
1120
1130
|
handler: "whitelist.removeEmail"
|
|
1121
1131
|
},
|
|
1122
1132
|
{
|
|
@@ -1518,9 +1528,9 @@ function whitelistService({ strapi: strapi2 }) {
|
|
|
1518
1528
|
data: { email }
|
|
1519
1529
|
});
|
|
1520
1530
|
},
|
|
1521
|
-
async removeUser(
|
|
1522
|
-
await getWhitelistQuery().
|
|
1523
|
-
where: {
|
|
1531
|
+
async removeUser(email) {
|
|
1532
|
+
await getWhitelistQuery().deleteMany({
|
|
1533
|
+
where: { email }
|
|
1524
1534
|
});
|
|
1525
1535
|
},
|
|
1526
1536
|
async checkWhitelistForEmail(email) {
|
package/package.json
CHANGED