@hed-hog/core 0.0.360 → 0.0.362

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 (153) hide show
  1. package/dist/access-log/access-log.controller.d.ts +1 -1
  2. package/dist/access-log/access-log.service.d.ts +1 -1
  3. package/dist/access-log/access-log.service.d.ts.map +1 -1
  4. package/dist/access-log/access-log.service.js +43 -1
  5. package/dist/access-log/access-log.service.js.map +1 -1
  6. package/dist/ai/ai.service.d.ts +1 -1
  7. package/dist/auth/auth.controller.d.ts +27 -26
  8. package/dist/auth/auth.controller.d.ts.map +1 -1
  9. package/dist/auth/auth.controller.js +6 -2
  10. package/dist/auth/auth.controller.js.map +1 -1
  11. package/dist/auth/auth.service.d.ts +35 -34
  12. package/dist/auth/auth.service.d.ts.map +1 -1
  13. package/dist/auth/auth.service.js +15 -5
  14. package/dist/auth/auth.service.js.map +1 -1
  15. package/dist/auth/guards/auth.guard.d.ts +1 -0
  16. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  17. package/dist/auth/guards/auth.guard.js +11 -4
  18. package/dist/auth/guards/auth.guard.js.map +1 -1
  19. package/dist/challenge/challenge.service.d.ts +12 -12
  20. package/dist/core/system.controller.d.ts +2 -2
  21. package/dist/core/system.service.d.ts +2 -2
  22. package/dist/dashboard/dashboard/dashboard.controller.d.ts +66 -66
  23. package/dist/dashboard/dashboard/dashboard.service.d.ts +66 -66
  24. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +26 -26
  25. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +26 -26
  26. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +34 -34
  27. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +34 -34
  28. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +8 -8
  29. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +8 -8
  30. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +5 -5
  31. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +5 -5
  32. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +25 -25
  33. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +25 -25
  34. package/dist/file/file.controller.d.ts +6 -6
  35. package/dist/file/file.module.d.ts.map +1 -1
  36. package/dist/file/file.module.js +3 -2
  37. package/dist/file/file.module.js.map +1 -1
  38. package/dist/file/file.service.d.ts +121 -25
  39. package/dist/file/file.service.d.ts.map +1 -1
  40. package/dist/file/file.service.js +217 -32
  41. package/dist/file/file.service.js.map +1 -1
  42. package/dist/file/image-optimization.service.d.ts +20 -0
  43. package/dist/file/image-optimization.service.d.ts.map +1 -0
  44. package/dist/file/image-optimization.service.js +126 -0
  45. package/dist/file/image-optimization.service.js.map +1 -0
  46. package/dist/file/provider/local.provider.d.ts +1 -1
  47. package/dist/file/provider/local.provider.d.ts.map +1 -1
  48. package/dist/file/provider/local.provider.js +2 -9
  49. package/dist/file/provider/local.provider.js.map +1 -1
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +1 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/integration-profile/integration-profile.controller.d.ts +15 -15
  55. package/dist/integration-profile/integration-profile.service.d.ts +15 -15
  56. package/dist/locale/locale.controller.d.ts +4 -4
  57. package/dist/locale/locale.service.d.ts +4 -4
  58. package/dist/mail/mail.controller.d.ts +7 -7
  59. package/dist/mail/mail.service.d.ts +7 -7
  60. package/dist/mail-sent/mail-sent.controller.d.ts +12 -12
  61. package/dist/mail-sent/mail-sent.service.d.ts +12 -12
  62. package/dist/mcp/mcp-api-key.controller.d.ts +2 -2
  63. package/dist/mcp/mcp-api-key.service.d.ts +2 -2
  64. package/dist/mcp/tools/core-users.mcp-tools.d.ts +1 -1
  65. package/dist/mcp/tools/core-users.mcp-tools.d.ts.map +1 -1
  66. package/dist/mcp/tools/core-users.mcp-tools.js +2 -2
  67. package/dist/mcp/tools/core-users.mcp-tools.js.map +1 -1
  68. package/dist/mcp-chat/mcp-chat.controller.d.ts +8 -8
  69. package/dist/mcp-chat/mcp-chat.service.d.ts +8 -8
  70. package/dist/menu/menu.controller.d.ts +10 -10
  71. package/dist/menu/menu.service.d.ts +10 -10
  72. package/dist/notification/notification.controller.d.ts +36 -36
  73. package/dist/notification/notification.service.d.ts +36 -36
  74. package/dist/profile/profile.controller.d.ts +16 -16
  75. package/dist/profile/profile.service.d.ts +16 -16
  76. package/dist/profile/profile.service.d.ts.map +1 -1
  77. package/dist/profile/profile.service.js +44 -23
  78. package/dist/profile/profile.service.js.map +1 -1
  79. package/dist/role/role.controller.d.ts +6 -6
  80. package/dist/role/role.service.d.ts +6 -6
  81. package/dist/screen/screen.controller.d.ts +3 -3
  82. package/dist/screen/screen.service.d.ts +3 -3
  83. package/dist/session/session.controller.d.ts +2 -2
  84. package/dist/session/session.service.d.ts +6 -6
  85. package/dist/session/session.service.d.ts.map +1 -1
  86. package/dist/session/session.service.js +7 -2
  87. package/dist/session/session.service.js.map +1 -1
  88. package/dist/setting/setting.controller.d.ts +18 -18
  89. package/dist/setting/setting.service.d.ts +18 -18
  90. package/dist/setting/setting.service.d.ts.map +1 -1
  91. package/dist/setting/setting.service.js +0 -1
  92. package/dist/setting/setting.service.js.map +1 -1
  93. package/dist/token/token.service.d.ts +1 -0
  94. package/dist/token/token.service.d.ts.map +1 -1
  95. package/dist/token/token.service.js +23 -6
  96. package/dist/token/token.service.js.map +1 -1
  97. package/dist/user/user.controller.d.ts +30 -30
  98. package/dist/user/user.controller.d.ts.map +1 -1
  99. package/dist/user/user.controller.js +4 -3
  100. package/dist/user/user.controller.js.map +1 -1
  101. package/dist/user/user.service.d.ts +68 -68
  102. package/dist/user/user.service.d.ts.map +1 -1
  103. package/dist/user/user.service.js +6 -6
  104. package/dist/user/user.service.js.map +1 -1
  105. package/dist/webhook-integration/webhook-integration.controller.d.ts +2 -2
  106. package/dist/webhook-integration/webhook-integration.service.d.ts +2 -2
  107. package/hedhog/data/route.yaml +2 -0
  108. package/hedhog/data/setting_group.yaml +117 -26
  109. package/hedhog/data/setting_subgroup.yaml +8 -0
  110. package/hedhog/frontend/app/access-log/page.tsx.ejs +170 -11
  111. package/hedhog/frontend/app/account/components/remove-mfa-dialog.tsx.ejs +1 -1
  112. package/hedhog/frontend/app/account/components/verify-before-add-dialog.tsx.ejs +1 -1
  113. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +113 -38
  114. package/hedhog/frontend/app/users/page.tsx.ejs +7 -7
  115. package/hedhog/frontend/app/users/permissions.tsx.ejs +137 -83
  116. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +28 -26
  117. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +207 -75
  118. package/hedhog/frontend/messages/en.json +9 -2
  119. package/hedhog/frontend/messages/pt.json +9 -2
  120. package/hedhog/table/setting_runtime_state.yaml +9 -0
  121. package/package.json +12 -6
  122. package/src/access-log/access-log.service.ts +19 -1
  123. package/src/auth/auth.controller.ts +5 -0
  124. package/src/auth/auth.service.ts +14 -4
  125. package/src/auth/guards/auth.guard.ts +10 -3
  126. package/src/file/file.module.ts +3 -2
  127. package/src/file/file.service.ts +448 -46
  128. package/src/file/image-optimization.service.ts +123 -0
  129. package/src/file/provider/local.provider.ts +2 -9
  130. package/src/index.ts +1 -0
  131. package/src/mcp/tools/core-users.mcp-tools.ts +267 -267
  132. package/src/profile/profile.service.ts +40 -22
  133. package/src/session/session.service.ts +10 -2
  134. package/src/setting/setting.service.ts +0 -1
  135. package/src/token/token.service.ts +26 -6
  136. package/src/user/user.controller.ts +5 -2
  137. package/src/user/user.service.ts +11 -8
  138. package/dist/menu/menu.service.spec.d.ts +0 -1
  139. package/dist/menu/menu.service.spec.d.ts.map +0 -1
  140. package/dist/menu/menu.service.spec.js +0 -208
  141. package/dist/menu/menu.service.spec.js.map +0 -1
  142. package/dist/route/route.service.spec.d.ts +0 -1
  143. package/dist/route/route.service.spec.d.ts.map +0 -1
  144. package/dist/route/route.service.spec.js +0 -262
  145. package/dist/route/route.service.spec.js.map +0 -1
  146. package/dist/screen/screen.service.spec.d.ts +0 -1
  147. package/dist/screen/screen.service.spec.d.ts.map +0 -1
  148. package/dist/screen/screen.service.spec.js +0 -259
  149. package/dist/screen/screen.service.spec.js.map +0 -1
  150. package/dist/user/user.service.spec.d.ts +0 -1
  151. package/dist/user/user.service.spec.d.ts.map +0 -1
  152. package/dist/user/user.service.spec.js +0 -261
  153. package/dist/user/user.service.spec.js.map +0 -1
@@ -29,6 +29,12 @@ import {
29
29
  } from '@/components/ui/form';
30
30
  import { FormActions } from '@/components/ui/form-actions';
31
31
  import { Input } from '@/components/ui/input';
32
+ import {
33
+ formatIntegrationProviderName,
34
+ IntegrationLogo,
35
+ normalizeIntegrationProvider,
36
+ resolveOAuthProvider,
37
+ } from '@/components/ui/integration-logo';
32
38
  import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
33
39
  import {
34
40
  Sheet,
@@ -36,6 +42,7 @@ import {
36
42
  SheetHeader,
37
43
  SheetTitle,
38
44
  } from '@/components/ui/sheet';
45
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
39
46
  import { useFormDraft } from '@/hooks/use-form-draft';
40
47
  import { formatDateTime } from '@/lib/format-date';
41
48
  import { getPhotoUrl } from '@/lib/get-photo-url';
@@ -44,7 +51,6 @@ import { User, UserMfa } from '@hed-hog/api-types';
44
51
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
45
52
  import { zodResolver } from '@hookform/resolvers/zod';
46
53
  import { Avatar, AvatarFallback, AvatarImage } from '@radix-ui/react-avatar';
47
- import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
48
54
  import { formatDistanceToNow } from 'date-fns';
49
55
  import { enUS, ptBR } from 'date-fns/locale';
50
56
  import {
@@ -103,10 +109,42 @@ type UserEditDraftPayload = {
103
109
 
104
110
  const USER_CREATE_DRAFT_STORAGE_KEY = 'core-user-create-draft';
105
111
 
106
- const accountProviders = [
107
- { name: 'Google', icon: '/google.svg' },
108
- { name: 'GitHub', icon: '/github.svg' },
109
- { name: 'Facebook', icon: '/facebook.svg' },
112
+ const OAUTH_PROVIDER_TOGGLE_MAP: Array<{
113
+ settingSlug: string;
114
+ provider: {
115
+ id: string;
116
+ slug: string;
117
+ name: string;
118
+ };
119
+ }> = [
120
+ {
121
+ settingSlug: 'oauth-facebook-enabled',
122
+ provider: { id: 'facebook', slug: 'facebook-oauth', name: 'Facebook' },
123
+ },
124
+ {
125
+ settingSlug: 'oauth-github-enabled',
126
+ provider: { id: 'github', slug: 'github-oauth', name: 'GitHub' },
127
+ },
128
+ {
129
+ settingSlug: 'oauth-google-enabled',
130
+ provider: { id: 'google', slug: 'google-oauth', name: 'Google' },
131
+ },
132
+ {
133
+ settingSlug: 'oauth-microsoft-enabled',
134
+ provider: {
135
+ id: 'microsoft',
136
+ slug: 'microsoft-oauth',
137
+ name: 'Microsoft',
138
+ },
139
+ },
140
+ {
141
+ settingSlug: 'oauth-microsoft-entra-id-enabled',
142
+ provider: {
143
+ id: 'microsoft-entra-id',
144
+ slug: 'microsoft-entra-id-oauth',
145
+ name: 'Microsoft Entra ID',
146
+ },
147
+ },
110
148
  ];
111
149
 
112
150
  function generateRandomPassword(length = 16) {
@@ -368,6 +406,13 @@ function UserEditSheet({
368
406
  const userActivityT = useTranslations('core.UserActivity');
369
407
  const { request, currentLocaleCode, getSettingValue } = useApp();
370
408
  const minPasswordLength = Number(getSettingValue('password-min-length')) || 6;
409
+ const enabledOAuthProviders = useMemo(
410
+ () =>
411
+ OAUTH_PROVIDER_TOGGLE_MAP.filter(
412
+ ({ settingSlug }) => getSettingValue(settingSlug) === true
413
+ ).map(({ provider }) => provider),
414
+ [getSettingValue]
415
+ );
371
416
 
372
417
  const [activeTab, setActiveTab] = useState('overview');
373
418
  const prevUserIdRef = useRef<number | null>(null);
@@ -419,6 +464,41 @@ function UserEditSheet({
419
464
  defaultValues: { password: '' },
420
465
  });
421
466
 
467
+ const passwordCredential = useMemo(() => {
468
+ const credentials = (editingUser as any)?.user_credential;
469
+ if (!Array.isArray(credentials) || credentials.length === 0) return null;
470
+
471
+ return (
472
+ credentials.find(
473
+ (credential) =>
474
+ String(credential?.type ?? '').toLowerCase() === 'password'
475
+ ) ?? credentials[0]
476
+ );
477
+ }, [editingUser]);
478
+
479
+ const passwordLastChangedStatus = useMemo(() => {
480
+ const changedAt =
481
+ passwordCredential?.updated_at ?? passwordCredential?.created_at ?? null;
482
+ if (!changedAt) return null;
483
+
484
+ const changedDate = new Date(changedAt);
485
+ if (Number.isNaN(changedDate.getTime())) return null;
486
+
487
+ const locale = currentLocaleCode === 'en' ? enUS : ptBR;
488
+
489
+ return {
490
+ relativeLabel: formatDistanceToNow(changedDate, {
491
+ addSuffix: true,
492
+ locale,
493
+ }),
494
+ absoluteLabel: formatDateTime(
495
+ changedDate,
496
+ getSettingValue,
497
+ currentLocaleCode
498
+ ),
499
+ };
500
+ }, [currentLocaleCode, getSettingValue, passwordCredential]);
501
+
422
502
  const watchedEditValues = useWatch({ control: editForm.control });
423
503
 
424
504
  const editDraftStorageKey = `core-user-edit-draft-${userId}`;
@@ -655,36 +735,38 @@ function UserEditSheet({
655
735
  </SheetHeader>
656
736
 
657
737
  <Tabs value={activeTab} onValueChange={setActiveTab}>
658
- <TabsList className="overflow-x-auto">
659
- <TabsTrigger value="overview">
660
- <UserCircle className="h-4 w-4" />
661
- <span className="hidden md:inline">{t('tabOverview')}</span>
662
- </TabsTrigger>
663
- <TabsTrigger value="edit">
664
- <Save className="h-4 w-4" />
665
- <span className="hidden md:inline">{t('tabEdit')}</span>
666
- </TabsTrigger>
667
- <TabsTrigger value="credentials">
668
- <Key className="h-4 w-4" />
669
- <span className="hidden md:inline">{t('tabCredentials')}</span>
670
- </TabsTrigger>
671
- <TabsTrigger value="identifiers">
672
- <Fingerprint className="h-4 w-4" />
673
- <span className="hidden md:inline">{t('tabIdentifiers')}</span>
674
- </TabsTrigger>
675
- <TabsTrigger value="permissions">
676
- <ShieldCheck className="h-4 w-4" />
677
- <span className="hidden md:inline">{t('tabPermissions')}</span>
678
- </TabsTrigger>
679
- <TabsTrigger value="mfa">
680
- <Shield className="h-4 w-4" />
681
- <span className="hidden md:inline">{t('tabMfa')}</span>
682
- </TabsTrigger>
683
- <TabsTrigger value="sessions">
684
- <Monitor className="h-4 w-4" />
685
- <span className="hidden md:inline">{t('tabSessions')}</span>
686
- </TabsTrigger>
687
- </TabsList>
738
+ <div className="sticky top-0 z-20 bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/80">
739
+ <TabsList className="flex-wrap overflow-visible px-2 py-1.5">
740
+ <TabsTrigger value="overview">
741
+ <UserCircle className="h-4 w-4" />
742
+ <span>{t('tabOverview')}</span>
743
+ </TabsTrigger>
744
+ <TabsTrigger value="edit">
745
+ <Save className="h-4 w-4" />
746
+ <span>{t('tabEdit')}</span>
747
+ </TabsTrigger>
748
+ <TabsTrigger value="credentials">
749
+ <Key className="h-4 w-4" />
750
+ <span>{t('tabCredentials')}</span>
751
+ </TabsTrigger>
752
+ <TabsTrigger value="identifiers">
753
+ <Fingerprint className="h-4 w-4" />
754
+ <span>{t('tabIdentifiers')}</span>
755
+ </TabsTrigger>
756
+ <TabsTrigger value="permissions">
757
+ <ShieldCheck className="h-4 w-4" />
758
+ <span>{t('tabPermissions')}</span>
759
+ </TabsTrigger>
760
+ <TabsTrigger value="mfa">
761
+ <Shield className="h-4 w-4" />
762
+ <span>{t('tabMfa')}</span>
763
+ </TabsTrigger>
764
+ <TabsTrigger value="sessions">
765
+ <Monitor className="h-4 w-4" />
766
+ <span>{t('tabSessions')}</span>
767
+ </TabsTrigger>
768
+ </TabsList>
769
+ </div>
688
770
 
689
771
  {/* Overview Tab */}
690
772
  <TabsContent value="overview" className="space-y-4 mt-4 p-4 pt-0">
@@ -1044,10 +1126,28 @@ function UserEditSheet({
1044
1126
  <h4 className="text-sm font-semibold">
1045
1127
  {t('passwordResetTitle')}
1046
1128
  </h4>
1047
- <p className="text-xs text-muted-foreground mt-1">
1129
+ <p className="mt-1 text-xs text-muted-foreground">
1048
1130
  {t('passwordResetDescription')}
1049
1131
  </p>
1050
1132
  </div>
1133
+ {passwordLastChangedStatus ? (
1134
+ <div className="rounded-md border border-border/60 bg-muted/30 px-3 py-2">
1135
+ <p className="text-xs font-medium text-foreground">
1136
+ {t('passwordLastChangedHumanized', {
1137
+ relativeLabel: passwordLastChangedStatus.relativeLabel,
1138
+ })}
1139
+ </p>
1140
+ <p className="mt-1 text-[11px] text-muted-foreground">
1141
+ {t('passwordLastChangedAbsolute', {
1142
+ absoluteLabel: passwordLastChangedStatus.absoluteLabel,
1143
+ })}
1144
+ </p>
1145
+ </div>
1146
+ ) : (
1147
+ <p className="text-xs text-muted-foreground">
1148
+ {t('passwordLastChangedUnavailable')}
1149
+ </p>
1150
+ )}
1051
1151
  <p className="text-xs text-muted-foreground">
1052
1152
  {t('passwordResetNotice')}
1053
1153
  </p>
@@ -1057,7 +1157,7 @@ function UserEditSheet({
1057
1157
  onClick={openResetPasswordDialog}
1058
1158
  className="w-full sm:w-fit"
1059
1159
  >
1060
- <RefreshCcw className="h-4 w-4 mr-2" />
1160
+ <RefreshCcw className="mr-2 h-4 w-4" />
1061
1161
  {t('buttonResetPassword')}
1062
1162
  </Button>
1063
1163
  </div>
@@ -1068,30 +1168,44 @@ function UserEditSheet({
1068
1168
  {t('connectedAccountsTitle')}
1069
1169
  </h4>
1070
1170
  <div className="space-y-2">
1071
- {accountProviders.map((provider) => {
1171
+ {enabledOAuthProviders.map((provider) => {
1172
+ const normalizedProviderId = resolveOAuthProvider(
1173
+ provider.id
1174
+ );
1072
1175
  const isConnected = editingUser.user_account?.some(
1073
1176
  (account) =>
1074
- account.provider?.toLowerCase() ===
1075
- provider.name.toLowerCase()
1177
+ resolveOAuthProvider(
1178
+ normalizeIntegrationProvider(
1179
+ String(account.provider ?? '')
1180
+ )
1181
+ ) === normalizedProviderId
1076
1182
  );
1183
+
1077
1184
  return (
1078
1185
  <div
1079
- key={provider.name}
1186
+ key={provider.slug}
1080
1187
  className={`flex items-center justify-between rounded-lg border p-3 ${isConnected ? '' : 'border-dashed'}`}
1081
1188
  >
1082
1189
  <div className="flex items-center gap-3">
1083
1190
  <div className="rounded-md bg-slate-50 p-2">
1084
- <img
1085
- src={provider.icon}
1086
- alt={`${provider.name} icon`}
1087
- className={`h-5 w-5 ${isConnected ? 'opacity-100' : 'opacity-50'}`}
1191
+ <IntegrationLogo
1192
+ provider={provider.slug}
1193
+ size={20}
1194
+ decorative={false}
1195
+ title={provider.name}
1196
+ className={
1197
+ isConnected ? 'opacity-100' : 'opacity-50'
1198
+ }
1088
1199
  />
1089
1200
  </div>
1090
1201
  <div>
1091
1202
  <p
1092
1203
  className={`text-sm font-medium ${isConnected ? '' : 'text-muted-foreground'}`}
1093
1204
  >
1094
- {provider.name}
1205
+ {formatIntegrationProviderName(
1206
+ provider.slug,
1207
+ provider.name
1208
+ )}
1095
1209
  </p>
1096
1210
  <p className="text-xs text-muted-foreground">
1097
1211
  {isConnected ? t('connected') : t('notConnected')}
@@ -1101,28 +1215,39 @@ function UserEditSheet({
1101
1215
  </div>
1102
1216
  );
1103
1217
  })}
1218
+ {enabledOAuthProviders.length === 0 && (
1219
+ <div className="rounded-lg border border-dashed p-3 text-sm text-muted-foreground">
1220
+ {t('noConnectedAccounts')}
1221
+ </div>
1222
+ )}
1104
1223
  </div>
1105
1224
  </div>
1106
1225
 
1107
- <AlertDialog
1226
+ <Sheet
1108
1227
  open={isResetPasswordDialogOpen}
1109
1228
  onOpenChange={(open) => {
1110
1229
  setIsResetPasswordDialogOpen(open);
1111
1230
  if (!open) setResetFormError(null);
1112
1231
  }}
1113
1232
  >
1114
- <AlertDialogContent>
1115
- <AlertDialogHeader>
1116
- <AlertDialogTitle>
1117
- {t('passwordResetDialogTitle')}
1118
- </AlertDialogTitle>
1119
- <AlertDialogDescription>
1233
+ <ResizableSheetContent
1234
+ sheetId="core-users-reset-password-sheet"
1235
+ defaultWidth={520}
1236
+ minWidth={420}
1237
+ maxWidth={760}
1238
+ className="w-full sm:max-w-lg overflow-y-auto gap-0"
1239
+ >
1240
+ <SheetHeader>
1241
+ <SheetTitle>{t('passwordResetDialogTitle')}</SheetTitle>
1242
+ <SheetDescription>
1120
1243
  {t('passwordResetDialogDescription')}
1121
- </AlertDialogDescription>
1122
- </AlertDialogHeader>
1244
+ </SheetDescription>
1245
+ </SheetHeader>
1246
+
1247
+ <div className="w-full border-t" />
1123
1248
 
1124
1249
  <Form {...resetPasswordForm}>
1125
- <form className="space-y-4">
1250
+ <form className="flex flex-col gap-4 p-4">
1126
1251
  <FormField
1127
1252
  control={resetPasswordForm.control}
1128
1253
  name="password"
@@ -1159,6 +1284,7 @@ function UserEditSheet({
1159
1284
  <Button
1160
1285
  type="button"
1161
1286
  variant="outline"
1287
+ className="w-full sm:w-fit"
1162
1288
  onClick={() => {
1163
1289
  resetPasswordForm.setValue(
1164
1290
  'password',
@@ -1186,27 +1312,33 @@ function UserEditSheet({
1186
1312
  <AlertDescription>{resetFormError}</AlertDescription>
1187
1313
  </Alert>
1188
1314
  )}
1315
+
1316
+ <div className="mt-auto flex flex-col gap-2 border-t pt-4 sm:flex-row sm:justify-end">
1317
+ <Button
1318
+ type="button"
1319
+ variant="outline"
1320
+ onClick={() => setIsResetPasswordDialogOpen(false)}
1321
+ >
1322
+ {t('cancel')}
1323
+ </Button>
1324
+ <Button
1325
+ type="button"
1326
+ disabled={isResettingPassword}
1327
+ onClick={async () => {
1328
+ const valid = await resetPasswordForm.trigger();
1329
+ if (!valid) return;
1330
+ await onResetPasswordSubmit();
1331
+ }}
1332
+ >
1333
+ {isResettingPassword
1334
+ ? t('passwordResetSubmitting')
1335
+ : t('passwordResetConfirm')}
1336
+ </Button>
1337
+ </div>
1189
1338
  </form>
1190
1339
  </Form>
1191
-
1192
- <AlertDialogFooter>
1193
- <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
1194
- <AlertDialogAction
1195
- disabled={isResettingPassword}
1196
- onClick={async (event) => {
1197
- event.preventDefault();
1198
- const valid = await resetPasswordForm.trigger();
1199
- if (!valid) return;
1200
- await onResetPasswordSubmit();
1201
- }}
1202
- >
1203
- {isResettingPassword
1204
- ? t('passwordResetSubmitting')
1205
- : t('passwordResetConfirm')}
1206
- </AlertDialogAction>
1207
- </AlertDialogFooter>
1208
- </AlertDialogContent>
1209
- </AlertDialog>
1340
+ </ResizableSheetContent>
1341
+ </Sheet>
1210
1342
 
1211
1343
  <AlertDialog
1212
1344
  open={isResetResultDialogOpen}
@@ -338,6 +338,9 @@
338
338
  "errorBulkRemovingRoles": "Error removing all roles",
339
339
  "passwordResetTitle": "Password Reset",
340
340
  "passwordResetDescription": "Generate a new password for this user.",
341
+ "passwordLastChangedHumanized": "This password has not been changed {relativeLabel}.",
342
+ "passwordLastChangedAbsolute": "Last recorded update: {absoluteLabel}",
343
+ "passwordLastChangedUnavailable": "The system could not determine when this password was last updated.",
341
344
  "passwordResetNotice": "After this reset, the user must change the password on the next login.",
342
345
  "buttonResetPassword": "Reset Password",
343
346
  "passwordResetDialogTitle": "Reset User Password",
@@ -1230,6 +1233,7 @@
1230
1233
  "fileUrlPlaceholder": "File URL",
1231
1234
  "selectOption": "Select an option",
1232
1235
  "selectMailProfile": "Select a mail profile",
1236
+ "mailProfileEmptyState": "No mail profiles found.",
1233
1237
  "selectWhatsAppProfile": "Select a WhatsApp profile",
1234
1238
  "selectStorageProfile": "Select a storage profile",
1235
1239
  "storageProfileEmptyState": "No storage profiles found.",
@@ -2226,7 +2230,10 @@
2226
2230
  "id": "ID",
2227
2231
  "actions": "Actions",
2228
2232
  "noUserAgent": "—",
2229
- "detailsDescription": "Full details of this access log entry"
2233
+ "detailsDescription": "Full details of this access log entry",
2234
+ "os": "Operating System",
2235
+ "location": "Location",
2236
+ "rawUserAgent": "User Agent"
2230
2237
  },
2231
2238
  "IntegrationProfilePage": {
2232
2239
  "breadcrumbHome": "Home",
@@ -2347,4 +2354,4 @@
2347
2354
  "showPassword": "Show value",
2348
2355
  "hidePassword": "Hide value"
2349
2356
  }
2350
- }
2357
+ }
@@ -338,6 +338,9 @@
338
338
  "errorBulkRemovingRoles": "Erro ao remover todos os cargos",
339
339
  "passwordResetTitle": "Redefinição de senha",
340
340
  "passwordResetDescription": "Gere uma nova senha para este usuário.",
341
+ "passwordLastChangedHumanized": "A senha não é alterada {relativeLabel}.",
342
+ "passwordLastChangedAbsolute": "Última atualização registrada: {absoluteLabel}",
343
+ "passwordLastChangedUnavailable": "Ainda não foi possível identificar quando esta senha foi atualizada pela última vez.",
341
344
  "passwordResetNotice": "Após essa redefinição, o usuário será obrigado a alterar a senha no próximo login.",
342
345
  "buttonResetPassword": "Redefinir senha",
343
346
  "passwordResetDialogTitle": "Redefinir senha do usuário",
@@ -1233,6 +1236,7 @@
1233
1236
  "fileUrlPlaceholder": "URL do arquivo",
1234
1237
  "selectOption": "Selecione uma opção",
1235
1238
  "selectMailProfile": "Selecione um perfil de email",
1239
+ "mailProfileEmptyState": "Nenhum perfil de email encontrado.",
1236
1240
  "selectWhatsAppProfile": "Selecione um perfil de WhatsApp",
1237
1241
  "selectStorageProfile": "Selecione um perfil de armazenamento",
1238
1242
  "storageProfileEmptyState": "Nenhum perfil de armazenamento encontrado.",
@@ -2281,7 +2285,10 @@
2281
2285
  "id": "ID",
2282
2286
  "actions": "Ações",
2283
2287
  "noUserAgent": "—",
2284
- "detailsDescription": "Detalhes completos desta entrada de log de acesso"
2288
+ "detailsDescription": "Detalhes completos desta entrada de log de acesso",
2289
+ "os": "Sistema Operacional",
2290
+ "location": "Localização",
2291
+ "rawUserAgent": "User Agent"
2285
2292
  },
2286
2293
  "IntegrationProfilePage": {
2287
2294
  "breadcrumbHome": "Início",
@@ -2402,4 +2409,4 @@
2402
2409
  "showPassword": "Mostrar valor",
2403
2410
  "hidePassword": "Ocultar valor"
2404
2411
  }
2405
- }
2412
+ }
@@ -0,0 +1,9 @@
1
+ columns:
2
+ - name: scope
3
+ type: VARCHAR
4
+ length: 64
5
+ isPrimaryKey: true
6
+ - name: version
7
+ type: BIGINT
8
+ default: 0
9
+ - type: updated_at
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@hed-hog/core",
3
- "version": "0.0.360",
3
+ "version": "0.0.362",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
7
+ "@aws-sdk/client-lambda": "3.1014.0",
7
8
  "@aws-sdk/client-s3": "^3.1014.0",
9
+ "@aws-sdk/client-sts": "3.1014.0",
8
10
  "@aws-sdk/s3-request-presigner": "^3.1014.0",
9
11
  "@azure/storage-blob": "^12.27.0",
10
12
  "@google-cloud/storage": "^7.16.0",
13
+ "@modelcontextprotocol/sdk": "^1.12.0",
11
14
  "@nestjs/axios": "*",
12
15
  "@nestjs/common": "*",
13
16
  "@nestjs/config": "^4.0.2",
@@ -19,6 +22,7 @@
19
22
  "argon2": "^0.44.0",
20
23
  "axios": "^1.9.0",
21
24
  "cookie-parser": "^1.4.7",
25
+ "geoip-lite": "^2.0.2",
22
26
  "handlebars": "^4.7.8",
23
27
  "jsonwebtoken": "^9.0.2",
24
28
  "multer": "^2.0.1",
@@ -31,14 +35,14 @@
31
35
  "rxjs": "^7.8.2",
32
36
  "sharp": "^0.34.2",
33
37
  "speakeasy": "^2.0.0",
38
+ "ua-parser-js": "^2.0.10",
34
39
  "uuid": "^11.1.0",
35
- "@modelcontextprotocol/sdk": "^1.12.0",
40
+ "@hed-hog/api": "0.0.8",
36
41
  "@hed-hog/api-locale": "0.0.14",
37
- "@hed-hog/api-mail": "0.0.9",
38
- "@hed-hog/api-pagination": "0.0.7",
39
42
  "@hed-hog/api-prisma": "0.0.6",
40
- "@hed-hog/api": "0.0.8",
41
- "@hed-hog/api-types": "0.0.1"
43
+ "@hed-hog/api-mail": "0.0.9",
44
+ "@hed-hog/api-types": "0.0.1",
45
+ "@hed-hog/api-pagination": "0.0.7"
42
46
  },
43
47
  "exports": {
44
48
  ".": {
@@ -71,8 +75,10 @@
71
75
  "devDependencies": {
72
76
  "@types/cookie-parser": "^1.4.10",
73
77
  "@types/express": "^4.17.24",
78
+ "@types/geoip-lite": "^1.4.4",
74
79
  "@types/multer": "^2.0.0",
75
80
  "@types/pako": "^2.0.4",
81
+ "@types/ua-parser-js": "^0.7.39",
76
82
  "rimraf": "^6.1.0"
77
83
  },
78
84
  "scripts": {
@@ -1,6 +1,8 @@
1
1
  import { PageOrderDirection, PaginationService } from '@hed-hog/api-pagination';
2
2
  import { Prisma, PrismaService } from '@hed-hog/api-prisma';
3
3
  import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
4
+ import * as geoip from 'geoip-lite';
5
+ import { UAParser } from 'ua-parser-js';
4
6
 
5
7
  interface RecordHttpData {
6
8
  userId?: number;
@@ -100,7 +102,7 @@ export class AccessLogService {
100
102
  where.mcp_tool = { not: null };
101
103
  }
102
104
 
103
- return this.pagination.paginate(this.prisma.access_log, {
105
+ const result = await this.pagination.paginate(this.prisma.access_log, {
104
106
  ...params,
105
107
  fields: params.fields ?? '',
106
108
  }, {
@@ -110,6 +112,22 @@ export class AccessLogService {
110
112
  user: { select: { id: true, name: true, photo_id: true } },
111
113
  },
112
114
  });
115
+
116
+ const data = (result.data as any[]).map((log) => {
117
+ const ua = log.user_agent ? new UAParser(log.user_agent).getResult() : null;
118
+ const geo = log.ip ? geoip.lookup(log.ip) : null;
119
+ return {
120
+ ...log,
121
+ browser_name: ua?.browser?.name ?? null,
122
+ browser_version: ua?.browser?.version ?? null,
123
+ os_name: ua?.os?.name ?? null,
124
+ os_version: ua?.os?.version ?? null,
125
+ country: geo?.country ?? null,
126
+ city: geo?.city ?? null,
127
+ };
128
+ });
129
+
130
+ return { ...result, data };
113
131
  }
114
132
 
115
133
  async clearOld(retentionDays: number, batchLimit: number): Promise<void> {
@@ -8,6 +8,7 @@ import {
8
8
  Headers,
9
9
  Inject,
10
10
  Ip,
11
+ Logger,
11
12
  Post,
12
13
  Req,
13
14
  Res,
@@ -28,6 +29,8 @@ import { ResetDTO } from './dto/reset.dto';
28
29
 
29
30
  @Controller('auth')
30
31
  export class AuthController {
32
+ private readonly logger = new Logger(AuthController.name);
33
+
31
34
  constructor(
32
35
  @Inject(forwardRef(() => AuthService))
33
36
  private readonly service: AuthService,
@@ -60,6 +63,8 @@ export class AuthController {
60
63
  @Headers('user-agent') userAgent: string,
61
64
  @Ip() ipAddress: string
62
65
  ) {
66
+ const tokenSource = req.cookies['rt'] ? 'cookie' : (refreshTokenFromBody ? 'body' : 'none');
67
+ this.logger.debug(`[AuthController] POST /auth/refresh tokenSource=${tokenSource} ip=${ipAddress}`);
63
68
  const currentRefreshToken = req.cookies['rt'] || refreshTokenFromBody;
64
69
  const { session, refreshToken, accessToken } = await this.service.refreshAccessToken(locale, currentRefreshToken, ipAddress, userAgent);
65
70
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);