@hed-hog/core 0.0.7 → 0.0.10
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/dist/auth/auth.service.d.ts +1 -4
- package/dist/auth/auth.service.d.ts.map +1 -1
- package/dist/auth/auth.service.js +9 -19
- package/dist/auth/auth.service.js.map +1 -1
- package/dist/challenge/challenge.service.d.ts +1 -4
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +26 -16
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/file/file.service.d.ts +2 -5
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +40 -16
- package/dist/file/file.service.js.map +1 -1
- package/dist/mail/mail.service.d.ts +1 -4
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +10 -9
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
- package/dist/oauth/oauth.service.d.ts +2 -5
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +12 -15
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.d.ts +2 -5
- package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +18 -15
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.d.ts +2 -5
- package/dist/oauth/providers/github.provider.d.ts.map +1 -1
- package/dist/oauth/providers/github.provider.js +17 -14
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.d.ts +2 -5
- package/dist/oauth/providers/google.provider.d.ts.map +1 -1
- package/dist/oauth/providers/google.provider.js +18 -15
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.d.ts +2 -5
- package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +17 -14
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/profile/profile.service.d.ts +0 -2
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +41 -20
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/session/session.service.d.ts +1 -4
- package/dist/session/session.service.d.ts.map +1 -1
- package/dist/session/session.service.js +4 -7
- package/dist/session/session.service.js.map +1 -1
- package/dist/setting/setting.controller.d.ts +3 -4
- package/dist/setting/setting.controller.d.ts.map +1 -1
- package/dist/setting/setting.controller.js +7 -3
- package/dist/setting/setting.controller.js.map +1 -1
- package/dist/setting/setting.service.d.ts +2 -4
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +45 -5
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/token/token.service.d.ts +1 -4
- package/dist/token/token.service.d.ts.map +1 -1
- package/dist/token/token.service.js +8 -11
- package/dist/token/token.service.js.map +1 -1
- package/hedhog/data/setting_group.yaml +11 -20
- package/package.json +7 -5
- package/src/auth/auth.service.ts +11 -22
- package/src/challenge/challenge.service.ts +35 -19
- package/src/file/file.service.ts +45 -20
- package/src/mail/mail.service.ts +16 -12
- package/src/oauth/interfaces/OAuthProvider.ts +1 -1
- package/src/oauth/oauth.service.ts +16 -19
- package/src/oauth/providers/abstract.provider.ts +1 -1
- package/src/oauth/providers/facebook.provider.ts +22 -18
- package/src/oauth/providers/github.provider.ts +22 -18
- package/src/oauth/providers/google.provider.ts +23 -17
- package/src/oauth/providers/microsoft.provider.ts +23 -18
- package/src/profile/profile.service.ts +52 -27
- package/src/session/session.service.ts +6 -11
- package/src/setting/setting.controller.ts +9 -3
- package/src/setting/setting.service.ts +18 -9
- package/src/token/token.service.ts +12 -15
|
@@ -3,14 +3,14 @@ import { PrismaService, user } from '@hed-hog/api-prisma';
|
|
|
3
3
|
import { BadRequestException, forwardRef, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
|
4
4
|
import { JwtService } from '@nestjs/jwt';
|
|
5
5
|
import type {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
VerifiedAuthenticationResponse,
|
|
7
|
+
VerifiedRegistrationResponse,
|
|
8
8
|
} from '@simplewebauthn/server';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
generateAuthenticationOptions,
|
|
11
|
+
generateRegistrationOptions,
|
|
12
|
+
verifyAuthenticationResponse,
|
|
13
|
+
verifyRegistrationResponse,
|
|
14
14
|
} from '@simplewebauthn/server';
|
|
15
15
|
import * as qrcode from 'qrcode';
|
|
16
16
|
import * as speakeasy from 'speakeasy';
|
|
@@ -27,7 +27,6 @@ import { UpdateDto } from './dto/update.dto';
|
|
|
27
27
|
|
|
28
28
|
@Injectable()
|
|
29
29
|
export class ProfileService {
|
|
30
|
-
public settings: Record<string, any> = {};
|
|
31
30
|
|
|
32
31
|
constructor(
|
|
33
32
|
private readonly prisma: PrismaService,
|
|
@@ -53,21 +52,6 @@ export class ProfileService {
|
|
|
53
52
|
return { success: true };
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
async onModuleInit() {
|
|
57
|
-
this.settings = await this.setting.getSettingValues([
|
|
58
|
-
'mfa-issuer',
|
|
59
|
-
'mfa-window',
|
|
60
|
-
'mfa-setp',
|
|
61
|
-
'system-name',
|
|
62
|
-
'google_client_id',
|
|
63
|
-
'google_client_secret',
|
|
64
|
-
'google_scopes',
|
|
65
|
-
'url',
|
|
66
|
-
'mfa-challenge-expiration-minutes',
|
|
67
|
-
'access-token-expiration-minutes'
|
|
68
|
-
]);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
55
|
async confirmEmailVerification(locale: string, userId: number, { pin, hash, name, email }: EmailVerificationDto) {
|
|
72
56
|
const challenge = await this.challenge.verifyCode(locale, userId, pin, hash, name, email)
|
|
73
57
|
const updatedUser = await this.prisma.user.findFirst({ where: { id: userId }})
|
|
@@ -187,8 +171,22 @@ export class ProfileService {
|
|
|
187
171
|
}
|
|
188
172
|
|
|
189
173
|
async verifyMfaTotp(userId: number, token: string, secret: string, name?: string) {
|
|
190
|
-
|
|
191
|
-
const
|
|
174
|
+
|
|
175
|
+
const settings = await this.setting.getSettingValues([
|
|
176
|
+
'mfa-issuer',
|
|
177
|
+
'mfa-window',
|
|
178
|
+
'mfa-setp',
|
|
179
|
+
'system-name',
|
|
180
|
+
'google_client_id',
|
|
181
|
+
'google_client_secret',
|
|
182
|
+
'google_scopes',
|
|
183
|
+
'url',
|
|
184
|
+
'mfa-challenge-expiration-minutes',
|
|
185
|
+
'access-token-expiration-minutes'
|
|
186
|
+
]);
|
|
187
|
+
|
|
188
|
+
const window = settings['mfa-window'] ?? 0;
|
|
189
|
+
const step = settings['mfa-setp'] ?? 30;
|
|
192
190
|
const user = await this.prisma.user.findFirst({
|
|
193
191
|
where: { id: userId },
|
|
194
192
|
include: { user_mfa: { include: { user_mfa_totp: true }}}
|
|
@@ -242,6 +240,20 @@ export class ProfileService {
|
|
|
242
240
|
}
|
|
243
241
|
|
|
244
242
|
async generateMfaTotp(locale: string, userId: number) {
|
|
243
|
+
|
|
244
|
+
const settings = await this.setting.getSettingValues([
|
|
245
|
+
'mfa-issuer',
|
|
246
|
+
'mfa-window',
|
|
247
|
+
'mfa-setp',
|
|
248
|
+
'system-name',
|
|
249
|
+
'google_client_id',
|
|
250
|
+
'google_client_secret',
|
|
251
|
+
'google_scopes',
|
|
252
|
+
'url',
|
|
253
|
+
'mfa-challenge-expiration-minutes',
|
|
254
|
+
'access-token-expiration-minutes'
|
|
255
|
+
]);
|
|
256
|
+
|
|
245
257
|
const user = await this.prisma.user.findFirst({
|
|
246
258
|
where: { id: userId },
|
|
247
259
|
select: {
|
|
@@ -250,7 +262,7 @@ export class ProfileService {
|
|
|
250
262
|
},
|
|
251
263
|
});
|
|
252
264
|
|
|
253
|
-
const issuer =
|
|
265
|
+
const issuer = settings['mfa-issuer'] ?? 'Hedhog';
|
|
254
266
|
const userEmail = user.user_identifier.find(ui => ui.type === 'email').value
|
|
255
267
|
const appName = `${issuer} (${userEmail})`;
|
|
256
268
|
|
|
@@ -261,7 +273,7 @@ export class ProfileService {
|
|
|
261
273
|
encoding: 'base32',
|
|
262
274
|
});
|
|
263
275
|
|
|
264
|
-
const step =
|
|
276
|
+
const step = settings['mfa-setp'] ?? 30;
|
|
265
277
|
const otpauth = speakeasy.otpauthURL({
|
|
266
278
|
secret: secret.base32,
|
|
267
279
|
label: `${issuer}:${userEmail}`,
|
|
@@ -946,7 +958,20 @@ export class ProfileService {
|
|
|
946
958
|
throw new NotFoundException(getLocaleText('userNotFound', locale, 'User not found.'));
|
|
947
959
|
}
|
|
948
960
|
|
|
949
|
-
const
|
|
961
|
+
const settings = await this.setting.getSettingValues([
|
|
962
|
+
'mfa-issuer',
|
|
963
|
+
'mfa-window',
|
|
964
|
+
'mfa-setp',
|
|
965
|
+
'system-name',
|
|
966
|
+
'google_client_id',
|
|
967
|
+
'google_client_secret',
|
|
968
|
+
'google_scopes',
|
|
969
|
+
'url',
|
|
970
|
+
'mfa-challenge-expiration-minutes',
|
|
971
|
+
'access-token-expiration-minutes'
|
|
972
|
+
]);
|
|
973
|
+
|
|
974
|
+
const rpName = settings['system-name'] || 'HedHog';
|
|
950
975
|
const rpID = new URL(process.env.FRONTEND_URL || 'http://localhost:3200').hostname;
|
|
951
976
|
const userEmail = user.user_identifier.find(ui => ui.type === 'email')?.value || `user${userId}@example.com`;
|
|
952
977
|
const existingAuthenticators = user.user_mfa
|
|
@@ -2,7 +2,7 @@ import { getLocaleText } from '@hed-hog/api-locale';
|
|
|
2
2
|
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
3
3
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
4
4
|
import { HttpService } from '@nestjs/axios';
|
|
5
|
-
import { BadRequestException, forwardRef, HttpException, HttpStatus, Inject, Injectable
|
|
5
|
+
import { BadRequestException, forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
|
6
6
|
import { firstValueFrom } from 'rxjs';
|
|
7
7
|
import { SecurityService } from '../security/security.service';
|
|
8
8
|
import { SettingService } from '../setting/setting.service';
|
|
@@ -21,9 +21,7 @@ type GeoIpResult = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
@Injectable()
|
|
24
|
-
export class SessionService
|
|
25
|
-
|
|
26
|
-
public settings: Record<string, any> = {};
|
|
24
|
+
export class SessionService {
|
|
27
25
|
|
|
28
26
|
constructor(
|
|
29
27
|
private prisma: PrismaService,
|
|
@@ -41,12 +39,6 @@ export class SessionService implements OnModuleInit {
|
|
|
41
39
|
private readonly paginationService: PaginationService,
|
|
42
40
|
) { }
|
|
43
41
|
|
|
44
|
-
async onModuleInit() {
|
|
45
|
-
this.settings = await this.setting.getSettingValues([
|
|
46
|
-
'refresh-token-expiration-minutes'
|
|
47
|
-
]);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
42
|
async create(locale: string, userId: number, ipAddress: string, userAgent: string) {
|
|
51
43
|
if (!userId) {
|
|
52
44
|
throw new BadRequestException(getLocaleText('session.userIdRequired', locale, 'User ID is required to create a session.'));
|
|
@@ -60,6 +52,9 @@ export class SessionService implements OnModuleInit {
|
|
|
60
52
|
throw new BadRequestException(getLocaleText('session.userAgentRequired', locale, 'User agent is required to create a session.'));
|
|
61
53
|
}
|
|
62
54
|
|
|
55
|
+
const settings = await this.setting.getSettingValues([
|
|
56
|
+
'refresh-token-expiration-minutes'
|
|
57
|
+
]);
|
|
63
58
|
const token = await this.token.createOpaqueToken();
|
|
64
59
|
const hash = this.security.hashWithPepper(token)
|
|
65
60
|
const session = await this.prisma.user_session.create({
|
|
@@ -67,7 +62,7 @@ export class SessionService implements OnModuleInit {
|
|
|
67
62
|
user_id: userId,
|
|
68
63
|
ip_address: ipAddress,
|
|
69
64
|
user_agent: userAgent,
|
|
70
|
-
expires_at: new Date(Date.now() + ((
|
|
65
|
+
expires_at: new Date(Date.now() + ((settings['refresh-token-expiration-minutes'] || 43200) * 60 * 1000)),
|
|
71
66
|
hash
|
|
72
67
|
},
|
|
73
68
|
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Delete,
|
|
8
8
|
forwardRef,
|
|
9
9
|
Get,
|
|
10
|
+
Header,
|
|
10
11
|
Inject,
|
|
11
12
|
Param,
|
|
12
13
|
ParseIntPipe,
|
|
@@ -14,10 +15,12 @@ import {
|
|
|
14
15
|
Post,
|
|
15
16
|
Put,
|
|
16
17
|
Query,
|
|
18
|
+
Res,
|
|
17
19
|
UploadedFile,
|
|
18
|
-
UseInterceptors
|
|
20
|
+
UseInterceptors
|
|
19
21
|
} from '@nestjs/common';
|
|
20
22
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
23
|
+
import { Response } from 'express';
|
|
21
24
|
import { ConfirmImportDTO } from './dto/confirm-import.dto';
|
|
22
25
|
import { CreateDTO } from './dto/create.dto';
|
|
23
26
|
import { DeleteDTO } from './dto/delete.dto';
|
|
@@ -36,8 +39,11 @@ export class SettingsController {
|
|
|
36
39
|
) { }
|
|
37
40
|
|
|
38
41
|
@Get('export')
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
@Header('Content-Type', 'application/octet-stream')
|
|
43
|
+
@Header('Content-Disposition', 'attachment; filename="settings.hedhog"')
|
|
44
|
+
async exportSettings(@Query() {secrets = false}: ExportDto, @Res() res: Response) {
|
|
45
|
+
const compressedData = await this.settingService.exportSettings(secrets);
|
|
46
|
+
res.send(compressedData);
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
@Public()
|
|
@@ -3,11 +3,12 @@ import { getLocaleText, LocaleService } from '@hed-hog/api-locale';
|
|
|
3
3
|
import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
|
|
4
4
|
import { PrismaService, setting_type_enum } from '@hed-hog/api-prisma';
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
BadRequestException,
|
|
7
|
+
forwardRef,
|
|
8
|
+
Inject,
|
|
9
|
+
Injectable,
|
|
10
10
|
} from '@nestjs/common';
|
|
11
|
+
import * as pako from 'pako';
|
|
11
12
|
import { CreateDTO } from './dto/create.dto';
|
|
12
13
|
import { DeleteDTO } from './dto/delete.dto';
|
|
13
14
|
import { SettingDTO } from './dto/setting.dto';
|
|
@@ -27,7 +28,7 @@ export class SettingService {
|
|
|
27
28
|
private readonly localeService: LocaleService,
|
|
28
29
|
) { }
|
|
29
30
|
|
|
30
|
-
async exportSettings(includeSecrets = false) {
|
|
31
|
+
async exportSettings(includeSecrets = false): Promise<Buffer> {
|
|
31
32
|
const settings = await this.prismaService.setting.findMany({
|
|
32
33
|
where: {
|
|
33
34
|
...( !includeSecrets ? {
|
|
@@ -42,16 +43,20 @@ export class SettingService {
|
|
|
42
43
|
}
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
// Converte para JSON e compacta com gzip
|
|
47
|
+
const jsonString = JSON.stringify(settings);
|
|
48
|
+
const compressed = pako.gzip(jsonString);
|
|
49
|
+
return Buffer.from(compressed);
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
async importSettings(file: MulterFile) {
|
|
49
|
-
const content = file.buffer?.toString('utf-8') || '';
|
|
50
53
|
let json;
|
|
51
54
|
try {
|
|
52
|
-
|
|
55
|
+
// Descompacta o arquivo .hedhog
|
|
56
|
+
const decompressed = pako.ungzip(file.buffer, { to: 'string' });
|
|
57
|
+
json = JSON.parse(decompressed);
|
|
53
58
|
} catch (err) {
|
|
54
|
-
throw new BadRequestException(getLocaleText('
|
|
59
|
+
throw new BadRequestException(getLocaleText('invalidCompressedFile', 'en', 'File is not a valid .hedhog file or is corrupted.'));
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
if (!Array.isArray(json)) {
|
|
@@ -74,11 +79,15 @@ export class SettingService {
|
|
|
74
79
|
const validSlugs = slugsFromFile.filter((slug: string) => existingSlugs.includes(slug));
|
|
75
80
|
const invalidSlugs = slugsFromFile.filter((slug: string) => !existingSlugs.includes(slug));
|
|
76
81
|
|
|
82
|
+
// Filtra apenas os dados válidos para retornar
|
|
83
|
+
const validData = json.filter((item: any) => validSlugs.includes(item.slug));
|
|
84
|
+
|
|
77
85
|
return {
|
|
78
86
|
totalSettings: slugsFromFile.length,
|
|
79
87
|
validSettings: validSlugs.length,
|
|
80
88
|
invalidSlugs,
|
|
81
89
|
validSlugs,
|
|
90
|
+
fileData: validData,
|
|
82
91
|
};
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { getLocaleText } from "@hed-hog/api-locale";
|
|
2
|
-
import { ForbiddenException, forwardRef, Inject, Injectable
|
|
2
|
+
import { ForbiddenException, forwardRef, Inject, Injectable } from "@nestjs/common";
|
|
3
3
|
import { JwtService } from "@nestjs/jwt";
|
|
4
4
|
import { SecurityService } from "../security/security.service";
|
|
5
5
|
import { SettingService } from "../setting/setting.service";
|
|
6
6
|
|
|
7
7
|
@Injectable()
|
|
8
|
-
export class TokenService
|
|
9
|
-
|
|
10
|
-
public settings: Record<string, any> = {};
|
|
8
|
+
export class TokenService {
|
|
11
9
|
|
|
12
10
|
constructor(
|
|
13
11
|
@Inject(forwardRef(() => JwtService))
|
|
@@ -18,12 +16,6 @@ export class TokenService implements OnModuleInit {
|
|
|
18
16
|
private readonly setting: SettingService,
|
|
19
17
|
) { }
|
|
20
18
|
|
|
21
|
-
async onModuleInit() {
|
|
22
|
-
this.settings = await this.setting.getSettingValues([
|
|
23
|
-
'url'
|
|
24
|
-
]);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
19
|
async verify(locale: string, token: string) {
|
|
28
20
|
try {
|
|
29
21
|
return this.jwt.verifyAsync(token, { secret: this.security.getJwtSecret() });
|
|
@@ -42,10 +34,15 @@ export class TokenService implements OnModuleInit {
|
|
|
42
34
|
return this.security.randomOpaque(size);
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
private getCookieDomain()
|
|
46
|
-
|
|
37
|
+
private async getCookieDomain() {
|
|
38
|
+
|
|
39
|
+
const settings = await this.setting.getSettingValues([
|
|
40
|
+
'url'
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
if (settings['url']) {
|
|
47
44
|
try {
|
|
48
|
-
return new URL(
|
|
45
|
+
return new URL(settings['url']).hostname;
|
|
49
46
|
} catch {
|
|
50
47
|
return process.env.COOKIE_DOMAIN;
|
|
51
48
|
}
|
|
@@ -55,7 +52,7 @@ export class TokenService implements OnModuleInit {
|
|
|
55
52
|
|
|
56
53
|
async removeRefreshTokenCookie(res): Promise<void> {
|
|
57
54
|
|
|
58
|
-
const domain = this.getCookieDomain();
|
|
55
|
+
const domain = await this.getCookieDomain();
|
|
59
56
|
const isLocalhost = !domain || domain === 'localhost';
|
|
60
57
|
const cookieOptions: any = {
|
|
61
58
|
httpOnly: true,
|
|
@@ -69,7 +66,7 @@ export class TokenService implements OnModuleInit {
|
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
async setRefreshTokenCookie(locale: string, res, token: string, expires_at: Date): Promise<void> {
|
|
72
|
-
const domain = this.getCookieDomain();
|
|
69
|
+
const domain = await this.getCookieDomain();
|
|
73
70
|
const isLocalhost = !domain || domain === 'localhost';
|
|
74
71
|
const maxAge = expires_at.getTime() - Date.now();
|
|
75
72
|
const cookieOptions: any = {
|