rl-core-api 0.11.4 → 0.13.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.
@@ -30,6 +30,8 @@ export declare const envSchema: z.ZodObject<{
30
30
  JWT_ACCESS_EXPIRES: z.ZodDefault<z.ZodString>;
31
31
  JWT_PENDING_EXPIRES: z.ZodDefault<z.ZodString>;
32
32
  JWT_REFRESH_EXPIRES_MINUTES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
33
+ JWT_REFRESH_MOBILE_EXPIRES_MINUTES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
34
+ DEVICE_TRUST_EXPIRES_MINUTES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
33
35
  TOTP_ENC_KEY: z.ZodString;
34
36
  COOKIE_SECURE: z.ZodDefault<z.ZodPipe<z.ZodEnum<{
35
37
  true: "true";
@@ -37,6 +37,16 @@ exports.envSchema = zod_1.z
37
37
  .int()
38
38
  .positive()
39
39
  .default(360),
40
+ JWT_REFRESH_MOBILE_EXPIRES_MINUTES: zod_1.z.coerce
41
+ .number()
42
+ .int()
43
+ .positive()
44
+ .default(43_200),
45
+ DEVICE_TRUST_EXPIRES_MINUTES: zod_1.z.coerce
46
+ .number()
47
+ .int()
48
+ .positive()
49
+ .default(259_200),
40
50
  TOTP_ENC_KEY: zod_1.z
41
51
  .string()
42
52
  .length(64, "TOTP_ENC_KEY deve ter 64 chars hex (32 bytes)"),
@@ -4,6 +4,7 @@ import { EnvService } from "../../../core/config/env.service";
4
4
  import { MailerService } from "../../../core/mailer/mailer.service";
5
5
  import { PasswordTokenPurpose, PasswordTokenStatus, TokenService } from "./token.service";
6
6
  import { TotpSetup, TwoFactorService } from "./twoFactor.service";
7
+ import { ClientType } from "../presentation/commands/clientType.enum";
7
8
  import { LoginCommand } from "../presentation/commands/login.command";
8
9
  import { VerifyTwoFactorCommand } from "../presentation/commands/twoFactor.command";
9
10
  import { RbacService } from "../../rbac/domain/rbac.service";
@@ -27,6 +28,18 @@ export interface LoginSession {
27
28
  }
28
29
  export interface VerifiedSession extends LoginSession {
29
30
  backupCodes?: string[];
31
+ deviceToken?: string;
32
+ }
33
+ export interface DeviceSession extends LoginSession {
34
+ deviceToken: string;
35
+ }
36
+ export interface TrustedDevice {
37
+ id: string;
38
+ family: string;
39
+ createdAt: Date;
40
+ expiresAt: Date;
41
+ ipAddress?: string | null;
42
+ userAgent?: string | null;
30
43
  }
31
44
  export interface RefreshedTokens {
32
45
  accessToken: string;
@@ -54,8 +67,11 @@ export declare class AuthService {
54
67
  }>;
55
68
  verifyTwoFactor(dto: VerifyTwoFactorCommand, ctx: RequestCtx): Promise<VerifiedSession>;
56
69
  regenerateBackupCodes(userId: string, code: string): Promise<string[]>;
57
- refresh(rawToken: string | undefined, ctx: RequestCtx): Promise<RefreshedTokens>;
58
- logout(rawRefreshToken?: string): Promise<void>;
70
+ refresh(rawToken: string | undefined, ctx: RequestCtx, client?: ClientType): Promise<RefreshedTokens>;
71
+ loginWithDevice(rawToken: string | undefined, ctx: RequestCtx): Promise<DeviceSession>;
72
+ listTrustedDevices(userId: string): Promise<TrustedDevice[]>;
73
+ forgetTrustedDevice(userId: string, family: string): Promise<void>;
74
+ logout(rawRefreshToken?: string, rawDeviceToken?: string): Promise<void>;
59
75
  forgotPassword(email: string): Promise<void>;
60
76
  inspectPasswordToken(token: string): Promise<PasswordTokenCheck>;
61
77
  resetPassword(token: string, newPassword: string): Promise<void>;
@@ -20,6 +20,7 @@ const codedError_util_1 = require("../../../core/utils/codedError.util");
20
20
  const hash_util_1 = require("../../../core/utils/hash.util");
21
21
  const token_service_1 = require("./token.service");
22
22
  const twoFactor_service_1 = require("./twoFactor.service");
23
+ const clientType_enum_1 = require("../presentation/commands/clientType.enum");
23
24
  const rbac_service_1 = require("../../rbac/domain/rbac.service");
24
25
  const users_service_1 = require("../../users/domain/users.service");
25
26
  const BACKUP_FORMAT = /^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$/;
@@ -150,8 +151,12 @@ let AuthService = class AuthService {
150
151
  user.backupCodes = remainingBackup;
151
152
  }
152
153
  await this.users.save(user);
153
- const tokens = await this.finalizeLogin(user, ctx);
154
- return { ...tokens, backupCodes };
154
+ const client = dto.client ?? clientType_enum_1.ClientType.WEB;
155
+ const tokens = await this.finalizeLogin(user, ctx, client);
156
+ const deviceToken = client === clientType_enum_1.ClientType.MOBILE
157
+ ? await this.tokens.issueDeviceToken(user.id, ctx)
158
+ : undefined;
159
+ return { ...tokens, backupCodes, deviceToken };
155
160
  }
156
161
  async regenerateBackupCodes(userId, code) {
157
162
  const user = await this.users.findById(userId);
@@ -170,11 +175,11 @@ let AuthService = class AuthService {
170
175
  await this.users.save(user);
171
176
  return generated.plain;
172
177
  }
173
- async refresh(rawToken, ctx) {
178
+ async refresh(rawToken, ctx, client = clientType_enum_1.ClientType.WEB) {
174
179
  if (!rawToken) {
175
180
  throw new common_1.UnauthorizedException("Refresh token ausente");
176
181
  }
177
- const { userId, rawToken: newRefresh } = await this.tokens.rotateRefreshToken(rawToken, ctx);
182
+ const { userId, rawToken: newRefresh } = await this.tokens.rotateRefreshToken(rawToken, ctx, client);
178
183
  const user = await this.users.findById(userId);
179
184
  if (!user.isActive) {
180
185
  throw accountInactive();
@@ -182,11 +187,43 @@ let AuthService = class AuthService {
182
187
  const accessToken = await this.signAccessToken(user);
183
188
  return { accessToken, refreshToken: newRefresh };
184
189
  }
185
- async logout(rawRefreshToken) {
186
- if (!rawRefreshToken) {
187
- return;
190
+ async loginWithDevice(rawToken, ctx) {
191
+ if (!rawToken) {
192
+ throw new common_1.UnauthorizedException("Aparelho não reconhecido");
193
+ }
194
+ const { userId, rawToken: renewed } = await this.tokens.rotateDeviceToken(rawToken, ctx);
195
+ const user = await this.users.findById(userId);
196
+ if (!user.isActive) {
197
+ throw accountInactive();
198
+ }
199
+ if (user.isLocked(this.env.get("LOGIN_MAX_ATTEMPTS"))) {
200
+ throw accountLocked();
201
+ }
202
+ (0, auditContext_1.setAuditUser)(user.id);
203
+ const session = await this.finalizeLogin(user, ctx, clientType_enum_1.ClientType.MOBILE);
204
+ return { ...session, deviceToken: renewed };
205
+ }
206
+ async listTrustedDevices(userId) {
207
+ const devices = await this.tokens.listTrustedDevices(userId);
208
+ return devices.map((device) => ({
209
+ id: device.id,
210
+ family: device.family ?? device.id,
211
+ createdAt: device.createdAt,
212
+ expiresAt: device.expiresAt,
213
+ ipAddress: device.ipAddress,
214
+ userAgent: device.userAgent,
215
+ }));
216
+ }
217
+ async forgetTrustedDevice(userId, family) {
218
+ await this.tokens.revokeTrustedDevice(userId, family);
219
+ }
220
+ async logout(rawRefreshToken, rawDeviceToken) {
221
+ if (rawRefreshToken) {
222
+ await this.tokens.revokeByRawToken(rawRefreshToken);
223
+ }
224
+ if (rawDeviceToken) {
225
+ await this.tokens.revokeDeviceByRawToken(rawDeviceToken);
188
226
  }
189
- await this.tokens.revokeByRawToken(rawRefreshToken);
190
227
  }
191
228
  async forgotPassword(email) {
192
229
  const user = await this.users.findByEmail(email);
@@ -284,9 +321,9 @@ let AuthService = class AuthService {
284
321
  mustChangePassword: user.mustChangePassword,
285
322
  };
286
323
  }
287
- async finalizeLogin(user, ctx) {
324
+ async finalizeLogin(user, ctx, client) {
288
325
  const accessToken = await this.signAccessToken(user);
289
- const { rawToken } = await this.tokens.issueRefreshToken(user.id, undefined, ctx);
326
+ const { rawToken } = await this.tokens.issueRefreshToken(user.id, undefined, ctx, client);
290
327
  return {
291
328
  accessToken,
292
329
  refreshToken: rawToken,
@@ -1,6 +1,7 @@
1
1
  import { EnvService } from "../../../core/config/env.service";
2
2
  import { AuthTokenRepository } from "../infra/repositories/authToken.repository";
3
- import { TokenType } from "../infra/schema/authToken.schema";
3
+ import { AuthToken, TokenType } from "../infra/schema/authToken.schema";
4
+ import { ClientType } from "../presentation/commands/clientType.enum";
4
5
  export interface RotationResult {
5
6
  userId: string;
6
7
  rawToken: string;
@@ -29,17 +30,33 @@ export declare class TokenService {
29
30
  constructor(repo: AuthTokenRepository, env: EnvService);
30
31
  private persist;
31
32
  private minutesFromNow;
33
+ private refreshMinutes;
32
34
  issueRefreshToken(userId: string, family?: string, ctx?: {
33
35
  ipAddress?: string;
34
36
  userAgent?: string;
35
- }): Promise<{
37
+ }, client?: ClientType): Promise<{
36
38
  rawToken: string;
37
39
  family: string;
38
40
  }>;
39
41
  rotateRefreshToken(rawToken: string, ctx?: {
40
42
  ipAddress?: string;
41
43
  userAgent?: string;
42
- }): Promise<RotationResult>;
44
+ }, client?: ClientType): Promise<RotationResult>;
45
+ issueDeviceToken(userId: string, ctx?: {
46
+ ipAddress?: string;
47
+ userAgent?: string;
48
+ }, family?: string): Promise<string>;
49
+ rotateDeviceToken(rawToken: string, ctx?: {
50
+ ipAddress?: string;
51
+ userAgent?: string;
52
+ }): Promise<{
53
+ userId: string;
54
+ rawToken: string;
55
+ }>;
56
+ listTrustedDevices(userId: string): Promise<AuthToken[]>;
57
+ revokeAllDevices(userId: string): Promise<void>;
58
+ revokeTrustedDevice(userId: string, family: string): Promise<void>;
59
+ revokeDeviceByRawToken(rawToken: string): Promise<void>;
43
60
  revokeFamily(family: string): Promise<void>;
44
61
  revokeAllRefresh(userId: string): Promise<void>;
45
62
  revokeByRawToken(rawToken: string): Promise<void>;
@@ -18,6 +18,7 @@ const env_service_1 = require("../../../core/config/env.service");
18
18
  const hash_util_1 = require("../../../core/utils/hash.util");
19
19
  const authToken_repository_1 = require("../infra/repositories/authToken.repository");
20
20
  const authToken_schema_1 = require("../infra/schema/authToken.schema");
21
+ const clientType_enum_1 = require("../presentation/commands/clientType.enum");
21
22
  var PasswordTokenStatus;
22
23
  (function (PasswordTokenStatus) {
23
24
  PasswordTokenStatus["VALID"] = "valid";
@@ -49,7 +50,12 @@ let TokenService = TokenService_1 = class TokenService {
49
50
  minutesFromNow(minutes) {
50
51
  return new Date(Date.now() + minutes * 60_000);
51
52
  }
52
- async issueRefreshToken(userId, family, ctx) {
53
+ refreshMinutes(client) {
54
+ return client === clientType_enum_1.ClientType.MOBILE
55
+ ? this.env.get("JWT_REFRESH_MOBILE_EXPIRES_MINUTES")
56
+ : this.env.get("JWT_REFRESH_EXPIRES_MINUTES");
57
+ }
58
+ async issueRefreshToken(userId, family, ctx, client = clientType_enum_1.ClientType.WEB) {
53
59
  const rawToken = (0, hash_util_1.randomToken)(48);
54
60
  const fam = family ?? (0, uuid_1.v7)();
55
61
  await this.persist({
@@ -57,13 +63,13 @@ let TokenService = TokenService_1 = class TokenService {
57
63
  rawToken,
58
64
  type: authToken_schema_1.TokenType.REFRESH_TOKEN,
59
65
  family: fam,
60
- expiresAt: this.minutesFromNow(this.env.get("JWT_REFRESH_EXPIRES_MINUTES")),
66
+ expiresAt: this.minutesFromNow(this.refreshMinutes(client)),
61
67
  ipAddress: ctx?.ipAddress,
62
68
  userAgent: ctx?.userAgent,
63
69
  });
64
70
  return { rawToken, family: fam };
65
71
  }
66
- async rotateRefreshToken(rawToken, ctx) {
72
+ async rotateRefreshToken(rawToken, ctx, client = clientType_enum_1.ClientType.WEB) {
67
73
  const tokenHash = (0, hash_util_1.sha256)(rawToken);
68
74
  const stored = await this.repo.findOne({
69
75
  where: { tokenHash, type: authToken_schema_1.TokenType.REFRESH_TOKEN },
@@ -83,9 +89,80 @@ let TokenService = TokenService_1 = class TokenService {
83
89
  }
84
90
  stored.used = true;
85
91
  await this.repo.save(stored);
86
- const { rawToken: newRaw, family } = await this.issueRefreshToken(stored.userId, stored.family ?? undefined, ctx);
92
+ const { rawToken: newRaw, family } = await this.issueRefreshToken(stored.userId, stored.family ?? undefined, ctx, client);
87
93
  return { userId: stored.userId, rawToken: newRaw, family };
88
94
  }
95
+ async issueDeviceToken(userId, ctx, family) {
96
+ if (!family) {
97
+ await this.revokeAllDevices(userId);
98
+ }
99
+ const rawToken = (0, hash_util_1.randomToken)(48);
100
+ await this.persist({
101
+ userId,
102
+ rawToken,
103
+ type: authToken_schema_1.TokenType.DEVICE_TRUST,
104
+ family: family ?? (0, uuid_1.v7)(),
105
+ expiresAt: this.minutesFromNow(this.env.get("DEVICE_TRUST_EXPIRES_MINUTES")),
106
+ ipAddress: ctx?.ipAddress,
107
+ userAgent: ctx?.userAgent,
108
+ });
109
+ return rawToken;
110
+ }
111
+ async rotateDeviceToken(rawToken, ctx) {
112
+ const stored = await this.repo.findOne({
113
+ where: { tokenHash: (0, hash_util_1.sha256)(rawToken), type: authToken_schema_1.TokenType.DEVICE_TRUST },
114
+ });
115
+ if (!stored) {
116
+ throw new common_1.UnauthorizedException("Aparelho não reconhecido");
117
+ }
118
+ if (stored.used || stored.revoked) {
119
+ this.logger.warn(`Reuso de vínculo de aparelho detectado (família ${stored.family})`);
120
+ if (stored.family) {
121
+ await this.revokeFamily(stored.family);
122
+ }
123
+ throw new common_1.UnauthorizedException("Aparelho revogado por segurança");
124
+ }
125
+ if (stored.isExpired()) {
126
+ throw new common_1.UnauthorizedException("Vínculo do aparelho expirado");
127
+ }
128
+ stored.used = true;
129
+ await this.repo.save(stored);
130
+ const renewed = await this.issueDeviceToken(stored.userId, ctx, stored.family ?? undefined);
131
+ return { userId: stored.userId, rawToken: renewed };
132
+ }
133
+ async listTrustedDevices(userId) {
134
+ const tokens = await this.repo.find({
135
+ where: { userId, type: authToken_schema_1.TokenType.DEVICE_TRUST, revoked: false },
136
+ order: { createdAt: "DESC" },
137
+ });
138
+ const latestByFamily = new Map();
139
+ for (const token of tokens) {
140
+ const key = token.family ?? token.id;
141
+ if (!latestByFamily.has(key) && !token.isExpired()) {
142
+ latestByFamily.set(key, token);
143
+ }
144
+ }
145
+ return [...latestByFamily.values()];
146
+ }
147
+ async revokeAllDevices(userId) {
148
+ await this.repo.update({ userId, type: authToken_schema_1.TokenType.DEVICE_TRUST, revoked: false }, { revoked: true });
149
+ }
150
+ async revokeTrustedDevice(userId, family) {
151
+ await this.repo.update({ userId, family, type: authToken_schema_1.TokenType.DEVICE_TRUST }, { revoked: true });
152
+ }
153
+ async revokeDeviceByRawToken(rawToken) {
154
+ const stored = await this.repo.findOne({
155
+ where: { tokenHash: (0, hash_util_1.sha256)(rawToken), type: authToken_schema_1.TokenType.DEVICE_TRUST },
156
+ });
157
+ if (!stored) {
158
+ return;
159
+ }
160
+ if (stored.family) {
161
+ await this.revokeFamily(stored.family);
162
+ return;
163
+ }
164
+ await this.repo.update({ id: stored.id }, { revoked: true });
165
+ }
89
166
  async revokeFamily(family) {
90
167
  await this.repo.update({ family }, { revoked: true });
91
168
  }
@@ -4,7 +4,8 @@ export declare enum TokenType {
4
4
  FIRST_ACCESS = 1,
5
5
  TWO_FACTOR_EMAIL = 2,
6
6
  REFRESH_TOKEN = 3,
7
- TOTP_USED = 4
7
+ TOTP_USED = 4,
8
+ DEVICE_TRUST = 5
8
9
  }
9
10
  export declare class AuthToken extends BaseEntity {
10
11
  userId: string;
@@ -19,6 +19,7 @@ var TokenType;
19
19
  TokenType[TokenType["TWO_FACTOR_EMAIL"] = 2] = "TWO_FACTOR_EMAIL";
20
20
  TokenType[TokenType["REFRESH_TOKEN"] = 3] = "REFRESH_TOKEN";
21
21
  TokenType[TokenType["TOTP_USED"] = 4] = "TOTP_USED";
22
+ TokenType[TokenType["DEVICE_TRUST"] = 5] = "DEVICE_TRUST";
22
23
  })(TokenType || (exports.TokenType = TokenType = {}));
23
24
  let AuthToken = class AuthToken extends base_schema_1.BaseEntity {
24
25
  isExpired() {
@@ -5,16 +5,19 @@ import { AuthService } from "../domain/auth.service";
5
5
  import { PasswordTokenCheck } from "../domain/auth.service";
6
6
  import { SessionUser } from "../domain/auth.service";
7
7
  import { LoginChallenge } from "../domain/auth.service";
8
+ import { DeviceSession, TrustedDevice } from "../domain/auth.service";
8
9
  import { TotpSetup } from "../domain/twoFactor.service";
10
+ import { DeviceLoginCommand } from "./commands/deviceLogin.command";
9
11
  import { LoginCommand } from "./commands/login.command";
10
12
  import { ForgotPasswordCommand, ResetPasswordCommand } from "./commands/password.command";
11
- import { RefreshTokenCommand } from "./commands/refreshToken.command";
13
+ import { LogoutCommand, RefreshTokenCommand } from "./commands/refreshToken.command";
12
14
  import { PendingTokenCommand, RegenerateBackupCommand, VerifyTwoFactorCommand } from "./commands/twoFactor.command";
13
15
  export interface VerifyResponse {
14
16
  user: SessionUser;
15
17
  backupCodes?: string[];
16
18
  accessToken?: string;
17
19
  refreshToken?: string;
20
+ deviceToken?: string;
18
21
  }
19
22
  export interface RefreshResponse {
20
23
  ok: boolean;
@@ -32,7 +35,12 @@ export declare class AuthController {
32
35
  }>;
33
36
  verify(dto: VerifyTwoFactorCommand, req: Request, res: Response): Promise<VerifyResponse>;
34
37
  refresh(dto: RefreshTokenCommand, req: Request, res: Response): Promise<RefreshResponse>;
35
- logout(dto: RefreshTokenCommand, req: Request, res: Response): Promise<{
38
+ logout(dto: LogoutCommand, req: Request, res: Response): Promise<{
39
+ ok: boolean;
40
+ }>;
41
+ deviceLogin(dto: DeviceLoginCommand, req: Request): Promise<DeviceSession>;
42
+ devices(user: AuthenticatedUser): Promise<TrustedDevice[]>;
43
+ forgetDevice(user: AuthenticatedUser, family: string): Promise<{
36
44
  ok: boolean;
37
45
  }>;
38
46
  profile(user: AuthenticatedUser): AuthenticatedUser;
@@ -26,6 +26,7 @@ const ip_util_1 = require("../../../core/utils/ip.util");
26
26
  const auditable_decorator_1 = require("../../audit/presentation/decorators/auditable.decorator");
27
27
  const auth_service_1 = require("../domain/auth.service");
28
28
  const clientType_enum_1 = require("./commands/clientType.enum");
29
+ const deviceLogin_command_1 = require("./commands/deviceLogin.command");
29
30
  const login_command_1 = require("./commands/login.command");
30
31
  const password_command_1 = require("./commands/password.command");
31
32
  const refreshToken_command_1 = require("./commands/refreshToken.command");
@@ -52,10 +53,10 @@ let AuthController = class AuthController {
52
53
  return this.auth.requestEmailCode(dto.pendingToken);
53
54
  }
54
55
  async verify(dto, req, res) {
55
- const { accessToken, refreshToken, user, backupCodes } = await this.auth.verifyTwoFactor(dto, ctxFrom(req));
56
+ const { accessToken, refreshToken, user, backupCodes, deviceToken } = await this.auth.verifyTwoFactor(dto, ctxFrom(req));
56
57
  (0, requestLog_context_1.setRequestLogUser)(req, user.id);
57
58
  if (dto.client === clientType_enum_1.ClientType.MOBILE) {
58
- return { user, backupCodes, accessToken, refreshToken };
59
+ return { user, backupCodes, accessToken, refreshToken, deviceToken };
59
60
  }
60
61
  (0, cookie_util_1.setAuthCookies)(res, this.env, { accessToken, refreshToken });
61
62
  return { user, backupCodes };
@@ -63,7 +64,8 @@ let AuthController = class AuthController {
63
64
  async refresh(dto, req, res) {
64
65
  const cookies = req.cookies;
65
66
  const raw = dto.refreshToken ?? cookies?.[cookie_util_1.REFRESH_COOKIE];
66
- const tokens = await this.auth.refresh(raw, ctxFrom(req));
67
+ const client = dto.refreshToken ? clientType_enum_1.ClientType.MOBILE : clientType_enum_1.ClientType.WEB;
68
+ const tokens = await this.auth.refresh(raw, ctxFrom(req), client);
67
69
  if (dto.refreshToken) {
68
70
  return { ok: true, ...tokens };
69
71
  }
@@ -72,10 +74,22 @@ let AuthController = class AuthController {
72
74
  }
73
75
  async logout(dto, req, res) {
74
76
  const cookies = req.cookies;
75
- await this.auth.logout(dto.refreshToken ?? cookies?.[cookie_util_1.REFRESH_COOKIE]);
77
+ await this.auth.logout(dto.refreshToken ?? cookies?.[cookie_util_1.REFRESH_COOKIE], dto.deviceToken);
76
78
  (0, cookie_util_1.clearAuthCookies)(res, this.env);
77
79
  return { ok: true };
78
80
  }
81
+ async deviceLogin(dto, req) {
82
+ const session = await this.auth.loginWithDevice(dto.deviceToken, ctxFrom(req));
83
+ (0, requestLog_context_1.setRequestLogUser)(req, session.user.id);
84
+ return session;
85
+ }
86
+ devices(user) {
87
+ return this.auth.listTrustedDevices(user.id);
88
+ }
89
+ async forgetDevice(user, family) {
90
+ await this.auth.forgetTrustedDevice(user.id, family);
91
+ return { ok: true };
92
+ }
79
93
  profile(user) {
80
94
  return user;
81
95
  }
@@ -170,9 +184,50 @@ __decorate([
170
184
  __param(1, (0, common_1.Req)()),
171
185
  __param(2, (0, common_1.Res)({ passthrough: true })),
172
186
  __metadata("design:type", Function),
173
- __metadata("design:paramtypes", [refreshToken_command_1.RefreshTokenCommand, Object, Object]),
187
+ __metadata("design:paramtypes", [refreshToken_command_1.LogoutCommand, Object, Object]),
174
188
  __metadata("design:returntype", Promise)
175
189
  ], AuthController.prototype, "logout", null);
190
+ __decorate([
191
+ (0, public_decorator_1.Public)(),
192
+ (0, throttler_1.Throttle)({ default: { limit: 5, ttl: 60_000 } }),
193
+ (0, auditable_decorator_1.Auditable)("Login pelo aparelho confiável"),
194
+ (0, common_1.Post)("device/login"),
195
+ (0, swagger_1.ApiOperation)({
196
+ summary: "Login pelo aparelho confiável: sem senha e sem 2FA",
197
+ description: "O app apresenta o vínculo criado num login completo, depois de destravá-lo com a biometria. O vínculo rotaciona a cada uso.",
198
+ }),
199
+ (0, swagger_1.ApiOkResponse)({ type: sessionUser_response_1.DeviceLoginResponse }),
200
+ __param(0, (0, common_1.Body)()),
201
+ __param(1, (0, common_1.Req)()),
202
+ __metadata("design:type", Function),
203
+ __metadata("design:paramtypes", [deviceLogin_command_1.DeviceLoginCommand, Object]),
204
+ __metadata("design:returntype", Promise)
205
+ ], AuthController.prototype, "deviceLogin", null);
206
+ __decorate([
207
+ (0, common_1.UseGuards)(jwtAuth_guard_1.JwtAuthGuard),
208
+ (0, swagger_1.ApiBearerAuth)(),
209
+ (0, common_1.Get)("devices"),
210
+ (0, swagger_1.ApiOperation)({ summary: "Aparelhos confiáveis da conta" }),
211
+ (0, swagger_1.ApiOkResponse)({ type: sessionUser_response_1.TrustedDeviceResponse, isArray: true }),
212
+ __param(0, (0, currentUser_decorator_1.CurrentUser)()),
213
+ __metadata("design:type", Function),
214
+ __metadata("design:paramtypes", [Object]),
215
+ __metadata("design:returntype", Promise)
216
+ ], AuthController.prototype, "devices", null);
217
+ __decorate([
218
+ (0, common_1.UseGuards)(jwtAuth_guard_1.JwtAuthGuard),
219
+ (0, swagger_1.ApiBearerAuth)(),
220
+ (0, auditable_decorator_1.Auditable)("Esquecer aparelho confiável"),
221
+ (0, common_1.Delete)("devices/:family"),
222
+ (0, swagger_1.ApiOperation)({
223
+ summary: "Esquece um aparelho: o próximo acesso dele exige senha + 2FA",
224
+ }),
225
+ __param(0, (0, currentUser_decorator_1.CurrentUser)()),
226
+ __param(1, (0, common_1.Param)("family")),
227
+ __metadata("design:type", Function),
228
+ __metadata("design:paramtypes", [Object, String]),
229
+ __metadata("design:returntype", Promise)
230
+ ], AuthController.prototype, "forgetDevice", null);
176
231
  __decorate([
177
232
  (0, common_1.UseGuards)(jwtAuth_guard_1.JwtAuthGuard),
178
233
  (0, swagger_1.ApiBearerAuth)(),
@@ -0,0 +1,3 @@
1
+ export declare class DeviceLoginCommand {
2
+ deviceToken: string;
3
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DeviceLoginCommand = void 0;
13
+ const swagger_1 = require("@nestjs/swagger");
14
+ const class_validator_1 = require("class-validator");
15
+ class DeviceLoginCommand {
16
+ }
17
+ exports.DeviceLoginCommand = DeviceLoginCommand;
18
+ __decorate([
19
+ (0, swagger_1.ApiProperty)({ description: "Vínculo do aparelho, guardado no Keychain" }),
20
+ (0, class_validator_1.IsString)(),
21
+ (0, class_validator_1.MinLength)(32),
22
+ __metadata("design:type", String)
23
+ ], DeviceLoginCommand.prototype, "deviceToken", void 0);
@@ -1,3 +1,6 @@
1
1
  export declare class RefreshTokenCommand {
2
2
  refreshToken?: string;
3
3
  }
4
+ export declare class LogoutCommand extends RefreshTokenCommand {
5
+ deviceToken?: string;
6
+ }
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.RefreshTokenCommand = void 0;
12
+ exports.LogoutCommand = exports.RefreshTokenCommand = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
14
  const class_validator_1 = require("class-validator");
15
15
  class RefreshTokenCommand {
@@ -24,3 +24,15 @@ __decorate([
24
24
  (0, class_validator_1.IsString)(),
25
25
  __metadata("design:type", String)
26
26
  ], RefreshTokenCommand.prototype, "refreshToken", void 0);
27
+ class LogoutCommand extends RefreshTokenCommand {
28
+ }
29
+ exports.LogoutCommand = LogoutCommand;
30
+ __decorate([
31
+ (0, swagger_1.ApiProperty)({
32
+ required: false,
33
+ description: "Enviar apenas para esquecer o aparelho junto com a saída",
34
+ }),
35
+ (0, class_validator_1.IsOptional)(),
36
+ (0, class_validator_1.IsString)(),
37
+ __metadata("design:type", String)
38
+ ], LogoutCommand.prototype, "deviceToken", void 0);
@@ -16,4 +16,19 @@ export declare class VerifyTwoFactorResponse {
16
16
  backupCodes?: string[];
17
17
  accessToken?: string;
18
18
  refreshToken?: string;
19
+ deviceToken?: string;
20
+ }
21
+ export declare class DeviceLoginResponse {
22
+ user: SessionUserResponse;
23
+ accessToken: string;
24
+ refreshToken: string;
25
+ deviceToken: string;
26
+ }
27
+ export declare class TrustedDeviceResponse {
28
+ id: string;
29
+ family: string;
30
+ createdAt: Date;
31
+ expiresAt: Date;
32
+ ipAddress?: string | null;
33
+ userAgent?: string | null;
19
34
  }
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.VerifyTwoFactorResponse = exports.SessionUserResponse = void 0;
12
+ exports.TrustedDeviceResponse = exports.DeviceLoginResponse = exports.VerifyTwoFactorResponse = exports.SessionUserResponse = void 0;
13
13
  const swagger_1 = require("@nestjs/swagger");
14
14
  class SessionUserResponse {
15
15
  }
@@ -88,3 +88,60 @@ __decorate([
88
88
  }),
89
89
  __metadata("design:type", String)
90
90
  ], VerifyTwoFactorResponse.prototype, "refreshToken", void 0);
91
+ __decorate([
92
+ (0, swagger_1.ApiProperty)({
93
+ type: String,
94
+ required: false,
95
+ description: "Vínculo do aparelho (client=mobile). Guardar no Keychain: é o que abre os próximos acessos sem senha e sem 2FA",
96
+ }),
97
+ __metadata("design:type", String)
98
+ ], VerifyTwoFactorResponse.prototype, "deviceToken", void 0);
99
+ class DeviceLoginResponse {
100
+ }
101
+ exports.DeviceLoginResponse = DeviceLoginResponse;
102
+ __decorate([
103
+ (0, swagger_1.ApiProperty)({ type: SessionUserResponse }),
104
+ __metadata("design:type", SessionUserResponse)
105
+ ], DeviceLoginResponse.prototype, "user", void 0);
106
+ __decorate([
107
+ (0, swagger_1.ApiProperty)(),
108
+ __metadata("design:type", String)
109
+ ], DeviceLoginResponse.prototype, "accessToken", void 0);
110
+ __decorate([
111
+ (0, swagger_1.ApiProperty)(),
112
+ __metadata("design:type", String)
113
+ ], DeviceLoginResponse.prototype, "refreshToken", void 0);
114
+ __decorate([
115
+ (0, swagger_1.ApiProperty)({ description: "O vínculo rotaciona a cada uso: guarde este" }),
116
+ __metadata("design:type", String)
117
+ ], DeviceLoginResponse.prototype, "deviceToken", void 0);
118
+ class TrustedDeviceResponse {
119
+ }
120
+ exports.TrustedDeviceResponse = TrustedDeviceResponse;
121
+ __decorate([
122
+ (0, swagger_1.ApiProperty)({ format: "uuid" }),
123
+ __metadata("design:type", String)
124
+ ], TrustedDeviceResponse.prototype, "id", void 0);
125
+ __decorate([
126
+ (0, swagger_1.ApiProperty)({
127
+ format: "uuid",
128
+ description: "Identifica o aparelho ao longo das rotações — é o que se revoga",
129
+ }),
130
+ __metadata("design:type", String)
131
+ ], TrustedDeviceResponse.prototype, "family", void 0);
132
+ __decorate([
133
+ (0, swagger_1.ApiProperty)(),
134
+ __metadata("design:type", Date)
135
+ ], TrustedDeviceResponse.prototype, "createdAt", void 0);
136
+ __decorate([
137
+ (0, swagger_1.ApiProperty)(),
138
+ __metadata("design:type", Date)
139
+ ], TrustedDeviceResponse.prototype, "expiresAt", void 0);
140
+ __decorate([
141
+ (0, swagger_1.ApiProperty)({ required: false, nullable: true }),
142
+ __metadata("design:type", Object)
143
+ ], TrustedDeviceResponse.prototype, "ipAddress", void 0);
144
+ __decorate([
145
+ (0, swagger_1.ApiProperty)({ required: false, nullable: true }),
146
+ __metadata("design:type", Object)
147
+ ], TrustedDeviceResponse.prototype, "userAgent", void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rl-core-api",
3
- "version": "0.11.4",
3
+ "version": "0.13.0",
4
4
  "description": "Core NestJS: autenticação com 2FA, RBAC, auditoria, notificações e listagens com filtro dinâmico",
5
5
  "author": "Rodrigo Liberti",
6
6
  "license": "MIT",