@strapi/plugin-users-permissions 5.49.0 → 5.50.0

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.
Files changed (59) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/admin/src/pages/AdvancedSettings/index.jsx +1 -1
  3. package/admin/src/pages/Roles/pages/ListPage/index.jsx +1 -1
  4. package/admin/src/translations/ja.json +46 -7
  5. package/dist/admin/pages/AdvancedSettings/index.js +1 -1
  6. package/dist/admin/pages/AdvancedSettings/index.js.map +1 -1
  7. package/dist/admin/pages/AdvancedSettings/index.mjs +1 -1
  8. package/dist/admin/pages/AdvancedSettings/index.mjs.map +1 -1
  9. package/dist/admin/pages/Roles/pages/ListPage/index.js +1 -1
  10. package/dist/admin/pages/Roles/pages/ListPage/index.js.map +1 -1
  11. package/dist/admin/pages/Roles/pages/ListPage/index.mjs +1 -1
  12. package/dist/admin/pages/Roles/pages/ListPage/index.mjs.map +1 -1
  13. package/dist/admin/translations/ja.json.js +46 -7
  14. package/dist/admin/translations/ja.json.js.map +1 -1
  15. package/dist/admin/translations/ja.json.mjs +46 -7
  16. package/dist/admin/translations/ja.json.mjs.map +1 -1
  17. package/dist/server/controllers/auth.js +133 -114
  18. package/dist/server/controllers/auth.js.map +1 -1
  19. package/dist/server/controllers/auth.mjs +132 -113
  20. package/dist/server/controllers/auth.mjs.map +1 -1
  21. package/dist/server/controllers/validation/user.js +16 -9
  22. package/dist/server/controllers/validation/user.js.map +1 -1
  23. package/dist/server/controllers/validation/user.mjs +16 -9
  24. package/dist/server/controllers/validation/user.mjs.map +1 -1
  25. package/dist/server/routes/content-api/auth.js +16 -0
  26. package/dist/server/routes/content-api/auth.js.map +1 -1
  27. package/dist/server/routes/content-api/auth.mjs +16 -0
  28. package/dist/server/routes/content-api/auth.mjs.map +1 -1
  29. package/dist/server/routes/content-api/validation.js +29 -2
  30. package/dist/server/routes/content-api/validation.js.map +1 -1
  31. package/dist/server/routes/content-api/validation.mjs +29 -2
  32. package/dist/server/routes/content-api/validation.mjs.map +1 -1
  33. package/dist/server/services/jwt.js +8 -6
  34. package/dist/server/services/jwt.js.map +1 -1
  35. package/dist/server/services/jwt.mjs +8 -6
  36. package/dist/server/services/jwt.mjs.map +1 -1
  37. package/dist/server/services/users-permissions.js +8 -0
  38. package/dist/server/services/users-permissions.js.map +1 -1
  39. package/dist/server/services/users-permissions.mjs +8 -0
  40. package/dist/server/services/users-permissions.mjs.map +1 -1
  41. package/dist/server/strategies/users-permissions.js +8 -1
  42. package/dist/server/strategies/users-permissions.js.map +1 -1
  43. package/dist/server/strategies/users-permissions.mjs +8 -1
  44. package/dist/server/strategies/users-permissions.mjs.map +1 -1
  45. package/dist/server/utils/refresh-cookie-options.js +27 -0
  46. package/dist/server/utils/refresh-cookie-options.js.map +1 -0
  47. package/dist/server/utils/refresh-cookie-options.mjs +25 -0
  48. package/dist/server/utils/refresh-cookie-options.mjs.map +1 -0
  49. package/lint-staged.config.mjs +1 -0
  50. package/package.json +5 -5
  51. package/rollup.config.mjs +3 -3
  52. package/server/controllers/auth.js +162 -141
  53. package/server/controllers/validation/user.js +22 -9
  54. package/server/routes/content-api/auth.js +12 -0
  55. package/server/routes/content-api/validation.js +32 -2
  56. package/server/services/jwt.js +4 -3
  57. package/server/services/users-permissions.js +2 -0
  58. package/server/strategies/users-permissions.js +6 -1
  59. package/server/utils/refresh-cookie-options.js +20 -0
@@ -1,46 +1,85 @@
1
1
  var ja = {
2
- "BoundRoute.title": "Bound route to",
2
+ "BoundRoute.title": "バインドされたルート",
3
+ "components.Input.error.validation.email": "有効なメールアドレスではありません",
4
+ "components.Input.error.validation.json": "JSON形式と一致しません",
5
+ "components.Input.error.validation.max": "値が大きすぎます。",
6
+ "components.Input.error.validation.maxLength": "値が長すぎます。",
7
+ "components.Input.error.validation.min": "値が小さすぎます。",
8
+ "components.Input.error.validation.minLength": "値が短すぎます。",
9
+ "components.Input.error.validation.minSupMax": "最大値を超えることはできません。",
10
+ "components.Input.error.validation.regex": "値が正規表現と一致しません。",
11
+ "components.Input.error.validation.required": "この値は必須です。",
12
+ "components.Input.error.validation.unique": "この値はすでに使用されています。",
3
13
  "EditForm.inputSelect.description.role": "新しい認証されたユーザーが選択された役割にアタッチされます。",
4
14
  "EditForm.inputSelect.label.role": "認証されたユーザーのデフォルトの役割",
5
15
  "EditForm.inputToggle.description.email": "ユーザーが異なる認証プロバイダで同じ電子メールアドレスを使用して複数のアカウントを作成できないようにします。",
6
16
  "EditForm.inputToggle.description.email-confirmation": "有効(ON)にすると、新しい登録ユーザーに確認メールが送信されます。",
7
17
  "EditForm.inputToggle.description.email-confirmation-redirection": "あなたのEメールを確認したら、リダイレクト先を選択してください。",
18
+ "EditForm.inputToggle.description.email-reset-password": "アプリケーションのパスワードリセットページのURL",
8
19
  "EditForm.inputToggle.description.sign-up": "あなたの電子メールを確認した後、リダイレクト先を選択しました。",
9
20
  "EditForm.inputToggle.label.email": "メールアドレスごとに1つのアカウント",
10
21
  "EditForm.inputToggle.label.email-confirmation": "Eメールの確認を有効にする",
11
22
  "EditForm.inputToggle.label.email-confirmation-redirection": "リダイレクトURL",
23
+ "EditForm.inputToggle.label.email-reset-password": "パスワードリセットページ",
12
24
  "EditForm.inputToggle.label.sign-up": "申し込みを有効にする",
25
+ "EditForm.inputToggle.placeholder.email-confirmation-redirection": "例: https://yourfrontend.com/email-confirmation-redirection",
26
+ "EditForm.inputToggle.placeholder.email-reset-password": "例: https://yourfrontend.com/reset-password",
27
+ "EditPage.form.roles": "ロールの詳細",
28
+ "Email.template.data.loaded": "メールテンプレートを読み込みました",
29
+ "Email.template.email_confirmation": "メールアドレスの確認",
30
+ "Email.template.form.edit.label": "テンプレートを編集",
31
+ "Email.template.reset_password": "パスワードリセット",
32
+ "Email.template.table.action.label": "操作",
33
+ "Email.template.table.icon.label": "アイコン",
34
+ "Email.template.table.name.label": "名前",
35
+ "Form.advancedSettings.data.loaded": "高度な設定データを読み込みました",
13
36
  "HeaderNav.link.advancedSettings": "高度な設定",
14
37
  "HeaderNav.link.emailTemplates": "メールテンプレート",
15
38
  "HeaderNav.link.providers": "プロバイダー",
39
+ "notification.success.submit": "設定が更新されました",
40
+ "page.title": "設定 - ロール",
41
+ "plugin.description.long": "JWTに基づいた完全な認証プロセスでAPIを保護します。このプラグインには、ユーザーのグループ間で権限を管理できるACL戦略もあります。",
42
+ "plugin.description.short": "JWTに基づく完全な認証プロセスでAPIを保護する",
43
+ "plugin.name": "ロールと権限",
16
44
  "Plugin.permissions.plugins.description": "{name} 個のプラグインに対して許可されたすべてのアクションを定義する",
17
45
  "Plugins.header.description": "ルートにバインドされたアクションのみが以下にリストされています",
18
46
  "Plugins.header.title": "権限",
19
47
  "Policies.header.hint": "アプリケーションのアクションまたはプラグインのアクションを選択し、コグアイコンをクリックしてバインドされたルートを表示します",
20
48
  "Policies.header.title": "高度な設定",
21
49
  "PopUpForm.Email.email_templates.inputDescription": "変数の使用方法がわからない場合は、{link}",
50
+ "PopUpForm.Email.link.documentation": "ドキュメントをご覧ください。",
22
51
  "PopUpForm.Email.options.from.email.label": "送信者Eメール",
23
52
  "PopUpForm.Email.options.from.email.placeholder": "kai@doe.com",
24
53
  "PopUpForm.Email.options.from.name.label": "送信者名",
25
54
  "PopUpForm.Email.options.from.name.placeholder": "Kai Doe",
26
55
  "PopUpForm.Email.options.message.label": "メッセージ",
27
56
  "PopUpForm.Email.options.object.label": "件名",
57
+ "PopUpForm.Email.options.object.placeholder": "%APP_NAME% のメールアドレスを確認してください",
28
58
  "PopUpForm.Email.options.response_email.label": "応答メール",
29
59
  "PopUpForm.Email.options.response_email.placeholder": "kai@doe.com",
60
+ "PopUpForm.header.edit.email-templates": "メールテンプレートの編集",
61
+ "PopUpForm.header.edit.providers": "プロバイダーの編集",
30
62
  "PopUpForm.Providers.enabled.description": "無効にすると、ユーザーはこのプロバイダを使用できなくなります。",
31
63
  "PopUpForm.Providers.enabled.label": "有効にする",
32
64
  "PopUpForm.Providers.key.label": "クライアントID",
33
65
  "PopUpForm.Providers.key.placeholder": "TEXT",
34
66
  "PopUpForm.Providers.redirectURL.front-end.label": "フロントエンドアプリへのリダイレクトURL",
67
+ "PopUpForm.Providers.redirectURL.label": "{provider}アプリケーションの設定に追加するリダイレクトURL",
35
68
  "PopUpForm.Providers.secret.label": "クライアントの秘密",
36
69
  "PopUpForm.Providers.secret.placeholder": "TEXT",
37
- "PopUpForm.Providers.subdomain.label": "Host URI (Subdomain)",
70
+ "PopUpForm.Providers.subdomain.label": "ホストURI(サブドメイン)",
38
71
  "PopUpForm.Providers.subdomain.placeholder": "my.subdomain.com",
39
- "PopUpForm.header.edit.email-templates": "メールテンプレートの編集",
40
- "notification.success.submit": "設定が更新されました",
41
- "plugin.description.long": "JWTに基づいた完全な認証プロセスでAPIを保護します。このプラグインには、ユーザーのグループ間で権限を管理できるACL戦略もあります。",
42
- "plugin.description.short": "JWTに基づく完全な認証プロセスでAPIを保護する",
43
- "plugin.name": "ロールと権限"
72
+ "popUpWarning.button.cancel": "キャンセル",
73
+ "popUpWarning.button.confirm": "確認",
74
+ "popUpWarning.title": "確認してください",
75
+ "popUpWarning.warning.cancel": "変更をキャンセルしてもよろしいですか?",
76
+ "Providers.data.loaded": "プロバイダーを読み込みました",
77
+ "Providers.status": "ステータス",
78
+ "Roles.empty": "ロールがまだありません。",
79
+ "Roles.empty.search": "検索に一致するロールがありません。",
80
+ "Settings.roles.deleted": "ロールを削除しました",
81
+ "Settings.roles.edited": "ロールを編集しました",
82
+ "Settings.section-label": "ユーザーと権限プラグイン"
44
83
  };
45
84
 
46
85
  export { ja as default };
@@ -1 +1 @@
1
- {"version":3,"file":"ja.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"ja.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -5,8 +5,9 @@ var require$$0$1 = require('lodash');
5
5
  var require$$0 = require('lodash/fp');
6
6
  var require$$1 = require('@strapi/utils');
7
7
  var index = require('../utils/index.js');
8
+ var refreshCookieOptions = require('../utils/refresh-cookie-options.js');
8
9
  var auth$1 = require('./validation/auth.js');
9
- var require$$6 = require('grant');
10
+ var require$$7 = require('grant');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
 
@@ -14,7 +15,7 @@ var require$$0__default$2 = /*#__PURE__*/_interopDefault(require$$0$2);
14
15
  var require$$0__default$1 = /*#__PURE__*/_interopDefault(require$$0$1);
15
16
  var require$$0__default = /*#__PURE__*/_interopDefault(require$$0);
16
17
  var require$$1__default = /*#__PURE__*/_interopDefault(require$$1);
17
- var require$$6__default = /*#__PURE__*/_interopDefault(require$$6);
18
+ var require$$7__default = /*#__PURE__*/_interopDefault(require$$7);
18
19
 
19
20
  var auth;
20
21
  var hasRequiredAuth;
@@ -30,8 +31,10 @@ function requireAuth() {
30
31
  const { concat, compact, isArray } = require$$0__default.default;
31
32
  const utils = require$$1__default.default;
32
33
  const { getService } = index.__require();
34
+ const { buildRefreshCookieOptions } = refreshCookieOptions.__require();
33
35
  const { validateCallbackBody, validateRegisterBody, validateSendEmailConfirmationBody, validateForgotPasswordBody, validateResetPasswordBody, validateEmailConfirmationBody, validateChangePasswordBody } = auth$1.__require();
34
36
  const { ApplicationError, ValidationError, ForbiddenError } = utils.errors;
37
+ const { buildSessionMetadata, sanitizeSessionEntry, sortSessionsForDisplay } = utils;
35
38
  const sanitizeUser = (user, ctx)=>{
36
39
  const { auth } = ctx.state;
37
40
  const userSchema = strapi.getModel('plugin::users-permissions.user');
@@ -43,6 +46,82 @@ function requireAuth() {
43
46
  const { deviceId } = requestBody || {};
44
47
  return typeof deviceId === 'string' && deviceId.length > 0 ? deviceId : undefined;
45
48
  };
49
+ const buildSessionMetadataFromContext = (ctx)=>buildSessionMetadata({
50
+ userAgent: ctx.request.headers['user-agent']
51
+ });
52
+ const sendRefreshAuthResponse = async (strapi1, ctx, user, { metadata } = {})=>{
53
+ const deviceId = extractDeviceId(ctx.request.body);
54
+ const tokenOptions = {
55
+ type: 'refresh',
56
+ ...metadata ? {
57
+ metadata
58
+ } : {}
59
+ };
60
+ const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), deviceId, tokenOptions);
61
+ const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
62
+ if ('error' in access) {
63
+ throw new ApplicationError('Invalid credentials');
64
+ }
65
+ const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
66
+ const requestHttpOnly = ctx.request.header['x-strapi-refresh-cookie'] === 'httpOnly';
67
+ if (upSessions?.httpOnly || requestHttpOnly) {
68
+ const cookieName = upSessions.cookie?.name || 'strapi_up_refresh';
69
+ const isProduction = process.env.NODE_ENV === 'production';
70
+ ctx.cookies.set(cookieName, refresh.token, buildRefreshCookieOptions(upSessions, isProduction));
71
+ return ctx.send({
72
+ jwt: access.token,
73
+ user: await sanitizeUser(user, ctx)
74
+ });
75
+ }
76
+ return ctx.send({
77
+ jwt: access.token,
78
+ refreshToken: refresh.token,
79
+ user: await sanitizeUser(user, ctx)
80
+ });
81
+ };
82
+ const reissueTokensAfterPasswordChange = async (strapi1, ctx, user)=>{
83
+ const deviceId = extractDeviceId(ctx.request.body);
84
+ await strapi1.sessionManager('users-permissions').invalidateRefreshToken(String(user.id));
85
+ const newDeviceId = deviceId || crypto.randomUUID();
86
+ const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), newDeviceId, {
87
+ type: 'refresh'
88
+ });
89
+ const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
90
+ if ('error' in access) {
91
+ throw new ApplicationError('Invalid credentials');
92
+ }
93
+ return ctx.send({
94
+ jwt: access.token,
95
+ refreshToken: refresh.token,
96
+ user: await sanitizeUser(user, ctx)
97
+ });
98
+ };
99
+ const revokeLogoutSessions = async (strapi1, ctx, userId, { scope, deviceId, body })=>{
100
+ const sessionManager = strapi1.sessionManager('users-permissions');
101
+ const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
102
+ if (scope === 'all') {
103
+ await sessionManager.invalidateRefreshToken(userId);
104
+ return;
105
+ }
106
+ if (deviceId) {
107
+ await sessionManager.invalidateRefreshToken(userId, deviceId);
108
+ return;
109
+ }
110
+ let currentSessionId = ctx.state.session?.id;
111
+ const cookieName = upSessions?.cookie?.name || 'strapi_up_refresh';
112
+ const refreshToken = ctx.cookies.get(cookieName) || (typeof body.refreshToken === 'string' ? body.refreshToken : undefined);
113
+ if (refreshToken) {
114
+ const validation = await sessionManager.validateRefreshToken(refreshToken);
115
+ if (validation.isValid) {
116
+ currentSessionId = validation.sessionId;
117
+ }
118
+ }
119
+ if (currentSessionId) {
120
+ await sessionManager.revokeSessionById(userId, currentSessionId);
121
+ return;
122
+ }
123
+ await sessionManager.invalidateRefreshToken(userId);
124
+ };
46
125
  auth = ({ strapi: strapi1 })=>({
47
126
  async callback (ctx) {
48
127
  const provider = ctx.params.provider || 'local';
@@ -100,38 +179,8 @@ function requireAuth() {
100
179
  }
101
180
  const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
102
181
  if (mode === 'refresh') {
103
- const deviceId = extractDeviceId(ctx.request.body);
104
- const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), deviceId, {
105
- type: 'refresh'
106
- });
107
- const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
108
- if ('error' in access) {
109
- throw new ApplicationError('Invalid credentials');
110
- }
111
- const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
112
- const requestHttpOnly = ctx.request.header['x-strapi-refresh-cookie'] === 'httpOnly';
113
- if (upSessions?.httpOnly || requestHttpOnly) {
114
- const cookieName = upSessions.cookie?.name || 'strapi_up_refresh';
115
- const isProduction = process.env.NODE_ENV === 'production';
116
- const isSecure = typeof upSessions.cookie?.secure === 'boolean' ? upSessions.cookie?.secure : isProduction;
117
- const cookieOptions = {
118
- httpOnly: true,
119
- secure: isSecure,
120
- sameSite: upSessions.cookie?.sameSite ?? 'lax',
121
- path: upSessions.cookie?.path ?? '/',
122
- domain: upSessions.cookie?.domain,
123
- overwrite: true
124
- };
125
- ctx.cookies.set(cookieName, refresh.token, cookieOptions);
126
- return ctx.send({
127
- jwt: access.token,
128
- user: await sanitizeUser(user, ctx)
129
- });
130
- }
131
- return ctx.send({
132
- jwt: access.token,
133
- refreshToken: refresh.token,
134
- user: await sanitizeUser(user, ctx)
182
+ return sendRefreshAuthResponse(strapi1, ctx, user, {
183
+ metadata: buildSessionMetadataFromContext(ctx)
135
184
  });
136
185
  }
137
186
  return ctx.send({
@@ -149,38 +198,8 @@ function requireAuth() {
149
198
  }
150
199
  const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
151
200
  if (mode === 'refresh') {
152
- const deviceId = extractDeviceId(ctx.request.body);
153
- const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), deviceId, {
154
- type: 'refresh'
155
- });
156
- const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
157
- if ('error' in access) {
158
- throw new ApplicationError('Invalid credentials');
159
- }
160
- const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
161
- const requestHttpOnly = ctx.request.header['x-strapi-refresh-cookie'] === 'httpOnly';
162
- if (upSessions?.httpOnly || requestHttpOnly) {
163
- const cookieName = upSessions.cookie?.name || 'strapi_up_refresh';
164
- const isProduction = process.env.NODE_ENV === 'production';
165
- const isSecure = typeof upSessions.cookie?.secure === 'boolean' ? upSessions.cookie?.secure : isProduction;
166
- const cookieOptions = {
167
- httpOnly: true,
168
- secure: isSecure,
169
- sameSite: upSessions.cookie?.sameSite ?? 'lax',
170
- path: upSessions.cookie?.path ?? '/',
171
- domain: upSessions.cookie?.domain,
172
- overwrite: true
173
- };
174
- ctx.cookies.set(cookieName, refresh.token, cookieOptions);
175
- return ctx.send({
176
- jwt: access.token,
177
- user: await sanitizeUser(user, ctx)
178
- });
179
- }
180
- return ctx.send({
181
- jwt: access.token,
182
- refreshToken: refresh.token,
183
- user: await sanitizeUser(user, ctx)
201
+ return sendRefreshAuthResponse(strapi1, ctx, user, {
202
+ metadata: buildSessionMetadataFromContext(ctx)
184
203
  });
185
204
  }
186
205
  return ctx.send({
@@ -216,22 +235,7 @@ function requireAuth() {
216
235
  });
217
236
  const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
218
237
  if (mode === 'refresh') {
219
- const deviceId = extractDeviceId(ctx.request.body);
220
- // Invalidate all sessions when password changes for security
221
- await strapi1.sessionManager('users-permissions').invalidateRefreshToken(String(user.id));
222
- const newDeviceId = deviceId || crypto.randomUUID();
223
- const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), newDeviceId, {
224
- type: 'refresh'
225
- });
226
- const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
227
- if ('error' in access) {
228
- throw new ApplicationError('Invalid credentials');
229
- }
230
- return ctx.send({
231
- jwt: access.token,
232
- refreshToken: refresh.token,
233
- user: await sanitizeUser(user, ctx)
234
- });
238
+ return reissueTokensAfterPasswordChange(strapi1, ctx, user);
235
239
  }
236
240
  return ctx.send({
237
241
  jwt: getService('jwt').issue({
@@ -260,22 +264,7 @@ function requireAuth() {
260
264
  });
261
265
  const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
262
266
  if (mode === 'refresh') {
263
- const deviceId = extractDeviceId(ctx.request.body);
264
- // Invalidate all sessions when password is reset for security
265
- await strapi1.sessionManager('users-permissions').invalidateRefreshToken(String(user.id));
266
- const newDeviceId = deviceId || crypto.randomUUID();
267
- const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), newDeviceId, {
268
- type: 'refresh'
269
- });
270
- const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
271
- if ('error' in access) {
272
- throw new ApplicationError('Invalid credentials');
273
- }
274
- return ctx.send({
275
- jwt: access.token,
276
- refreshToken: refresh.token,
277
- user: await sanitizeUser(user, ctx)
278
- });
267
+ return reissueTokensAfterPasswordChange(strapi1, ctx, user);
279
268
  }
280
269
  return ctx.send({
281
270
  jwt: getService('jwt').issue({
@@ -310,16 +299,7 @@ function requireAuth() {
310
299
  const requestHttpOnly = ctx.request.header['x-strapi-refresh-cookie'] === 'httpOnly';
311
300
  if (upSessions?.httpOnly || requestHttpOnly) {
312
301
  const isProduction = process.env.NODE_ENV === 'production';
313
- const isSecure = typeof upSessions.cookie?.secure === 'boolean' ? upSessions.cookie?.secure : isProduction;
314
- const cookieOptions = {
315
- httpOnly: true,
316
- secure: isSecure,
317
- sameSite: upSessions.cookie?.sameSite ?? 'lax',
318
- path: upSessions.cookie?.path ?? '/',
319
- domain: upSessions.cookie?.domain,
320
- overwrite: true
321
- };
322
- ctx.cookies.set(cookieName, rotation.token, cookieOptions);
302
+ ctx.cookies.set(cookieName, rotation.token, buildRefreshCookieOptions(upSessions, isProduction));
323
303
  return ctx.send({
324
304
  jwt: result.token
325
305
  });
@@ -334,17 +314,23 @@ function requireAuth() {
334
314
  if (mode !== 'refresh') {
335
315
  return ctx.notFound();
336
316
  }
337
- // Invalidate all sessions for the authenticated user, or by deviceId if provided
338
317
  if (!ctx.state.user) {
339
318
  return ctx.unauthorized('Missing authentication');
340
319
  }
341
- const deviceId = extractDeviceId(ctx.request.body);
320
+ const userId = String(ctx.state.user.id);
321
+ const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
322
+ const body = ctx.request.body || {};
323
+ const scope = typeof body.scope === 'string' ? body.scope : undefined;
324
+ const deviceId = extractDeviceId(body);
342
325
  try {
343
- await strapi1.sessionManager('users-permissions').invalidateRefreshToken(String(ctx.state.user.id), deviceId);
326
+ await revokeLogoutSessions(strapi1, ctx, userId, {
327
+ scope,
328
+ deviceId,
329
+ body
330
+ });
344
331
  } catch (err) {
345
332
  strapi1.log.error('UP logout failed', err);
346
333
  }
347
- const upSessions = strapi1.config.get('plugin::users-permissions.sessions');
348
334
  const requestHttpOnly = ctx.request.header['x-strapi-refresh-cookie'] === 'httpOnly';
349
335
  if (upSessions?.httpOnly || requestHttpOnly) {
350
336
  const cookieName = upSessions.cookie?.name || 'strapi_up_refresh';
@@ -356,8 +342,40 @@ function requireAuth() {
356
342
  ok: true
357
343
  });
358
344
  },
345
+ async getSessions (ctx) {
346
+ const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
347
+ if (mode !== 'refresh') {
348
+ return ctx.notFound();
349
+ }
350
+ if (!ctx.state.user) {
351
+ return ctx.unauthorized('Missing authentication');
352
+ }
353
+ const currentSessionId = ctx.state.session?.id;
354
+ const sessions = await strapi1.sessionManager('users-permissions').listSessions(String(ctx.state.user.id));
355
+ const data = sortSessionsForDisplay(sessions.map((session)=>sanitizeSessionEntry(session, currentSessionId)));
356
+ return ctx.send({
357
+ data
358
+ });
359
+ },
360
+ async revokeSession (ctx) {
361
+ const mode = strapi1.config.get('plugin::users-permissions.jwtManagement', 'legacy-support');
362
+ if (mode !== 'refresh') {
363
+ return ctx.notFound();
364
+ }
365
+ if (!ctx.state.user) {
366
+ return ctx.unauthorized('Missing authentication');
367
+ }
368
+ const { sessionId } = ctx.params;
369
+ const revoked = await strapi1.sessionManager('users-permissions').revokeSessionById(String(ctx.state.user.id), sessionId);
370
+ if (!revoked) {
371
+ return ctx.notFound('Session not found');
372
+ }
373
+ return ctx.send({
374
+ data: {}
375
+ });
376
+ },
359
377
  async connect (ctx, next) {
360
- const grant = require$$6__default.default.koa();
378
+ const grant = require$$7__default.default.koa();
361
379
  const providers = await strapi1.store({
362
380
  type: 'plugin',
363
381
  name: 'users-permissions',
@@ -553,7 +571,8 @@ function requireAuth() {
553
571
  if (mode === 'refresh') {
554
572
  const deviceId = extractDeviceId(ctx.request.body) || crypto.randomUUID();
555
573
  const refresh = await strapi1.sessionManager('users-permissions').generateRefreshToken(String(user.id), deviceId, {
556
- type: 'refresh'
574
+ type: 'refresh',
575
+ metadata: buildSessionMetadataFromContext(ctx)
557
576
  });
558
577
  const access = await strapi1.sessionManager('users-permissions').generateAccessToken(refresh.token);
559
578
  if ('error' in access) {