@nauth-toolkit/core 0.1.13 → 0.1.17
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/adapters/database-columns.d.ts +70 -0
- package/dist/adapters/database-columns.d.ts.map +1 -1
- package/dist/adapters/database-columns.js +76 -2
- package/dist/adapters/database-columns.js.map +1 -1
- package/dist/adapters/express.adapter.d.ts +66 -0
- package/dist/adapters/express.adapter.d.ts.map +1 -1
- package/dist/adapters/express.adapter.js +80 -0
- package/dist/adapters/express.adapter.js.map +1 -1
- package/dist/adapters/fastify.adapter.d.ts +42 -0
- package/dist/adapters/fastify.adapter.d.ts.map +1 -1
- package/dist/adapters/fastify.adapter.js +86 -0
- package/dist/adapters/fastify.adapter.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +9 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/storage.factory.d.ts +107 -0
- package/dist/adapters/storage.factory.d.ts.map +1 -1
- package/dist/adapters/storage.factory.js +114 -0
- package/dist/adapters/storage.factory.js.map +1 -1
- package/dist/adapters.d.ts +8 -0
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +8 -0
- package/dist/adapters.js.map +1 -1
- package/dist/bootstrap.d.ts +82 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +106 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/dto/admin-set-password.dto.d.ts +90 -0
- package/dist/dto/admin-set-password.dto.d.ts.map +1 -1
- package/dist/dto/admin-set-password.dto.js +91 -0
- package/dist/dto/admin-set-password.dto.js.map +1 -1
- package/dist/dto/auth-challenge.dto.d.ts +170 -0
- package/dist/dto/auth-challenge.dto.d.ts.map +1 -1
- package/dist/dto/auth-challenge.dto.js +170 -0
- package/dist/dto/auth-challenge.dto.js.map +1 -1
- package/dist/dto/auth-response.dto.d.ts +196 -0
- package/dist/dto/auth-response.dto.d.ts.map +1 -1
- package/dist/dto/auth-response.dto.js +149 -0
- package/dist/dto/auth-response.dto.js.map +1 -1
- package/dist/dto/challenge-response.dto.d.ts +155 -0
- package/dist/dto/challenge-response.dto.d.ts.map +1 -1
- package/dist/dto/challenge-response.dto.js +8 -0
- package/dist/dto/challenge-response.dto.js.map +1 -1
- package/dist/dto/change-password-request.dto.d.ts +35 -0
- package/dist/dto/change-password-request.dto.d.ts.map +1 -1
- package/dist/dto/change-password-request.dto.js +35 -0
- package/dist/dto/change-password-request.dto.js.map +1 -1
- package/dist/dto/change-password-response.dto.d.ts +25 -0
- package/dist/dto/change-password-response.dto.d.ts.map +1 -1
- package/dist/dto/change-password-response.dto.js +25 -0
- package/dist/dto/change-password-response.dto.js.map +1 -1
- package/dist/dto/change-password.dto.d.ts +45 -0
- package/dist/dto/change-password.dto.d.ts.map +1 -1
- package/dist/dto/change-password.dto.js +45 -0
- package/dist/dto/change-password.dto.js.map +1 -1
- package/dist/dto/confirm-forgot-password.dto.d.ts +59 -0
- package/dist/dto/confirm-forgot-password.dto.d.ts.map +1 -1
- package/dist/dto/confirm-forgot-password.dto.js +59 -0
- package/dist/dto/confirm-forgot-password.dto.js.map +1 -1
- package/dist/dto/error-response.dto.d.ts +103 -0
- package/dist/dto/error-response.dto.d.ts.map +1 -1
- package/dist/dto/error-response.dto.js +103 -0
- package/dist/dto/error-response.dto.js.map +1 -1
- package/dist/dto/forgot-password.dto.d.ts +58 -0
- package/dist/dto/forgot-password.dto.d.ts.map +1 -1
- package/dist/dto/forgot-password.dto.js +58 -0
- package/dist/dto/forgot-password.dto.js.map +1 -1
- package/dist/dto/get-available-methods.dto.d.ts +37 -0
- package/dist/dto/get-available-methods.dto.d.ts.map +1 -1
- package/dist/dto/get-available-methods.dto.js +37 -0
- package/dist/dto/get-available-methods.dto.js.map +1 -1
- package/dist/dto/get-challenge-data-response.dto.d.ts +24 -0
- package/dist/dto/get-challenge-data-response.dto.d.ts.map +1 -1
- package/dist/dto/get-challenge-data-response.dto.js +24 -0
- package/dist/dto/get-challenge-data-response.dto.js.map +1 -1
- package/dist/dto/get-challenge-data.dto.d.ts +46 -0
- package/dist/dto/get-challenge-data.dto.d.ts.map +1 -1
- package/dist/dto/get-challenge-data.dto.js +46 -0
- package/dist/dto/get-challenge-data.dto.js.map +1 -1
- package/dist/dto/get-client-info.dto.d.ts +74 -0
- package/dist/dto/get-client-info.dto.d.ts.map +1 -1
- package/dist/dto/get-client-info.dto.js +74 -0
- package/dist/dto/get-client-info.dto.js.map +1 -1
- package/dist/dto/get-device-token-response.dto.d.ts +21 -0
- package/dist/dto/get-device-token-response.dto.d.ts.map +1 -1
- package/dist/dto/get-device-token-response.dto.js +21 -0
- package/dist/dto/get-device-token-response.dto.js.map +1 -1
- package/dist/dto/get-events-by-type.dto.d.ts +50 -0
- package/dist/dto/get-events-by-type.dto.d.ts.map +1 -1
- package/dist/dto/get-events-by-type.dto.js +50 -0
- package/dist/dto/get-events-by-type.dto.js.map +1 -1
- package/dist/dto/get-ip-address-response.dto.d.ts +20 -0
- package/dist/dto/get-ip-address-response.dto.d.ts.map +1 -1
- package/dist/dto/get-ip-address-response.dto.js +20 -0
- package/dist/dto/get-ip-address-response.dto.js.map +1 -1
- package/dist/dto/get-mfa-status.dto.d.ts +59 -0
- package/dist/dto/get-mfa-status.dto.d.ts.map +1 -1
- package/dist/dto/get-mfa-status.dto.js +59 -0
- package/dist/dto/get-mfa-status.dto.js.map +1 -1
- package/dist/dto/get-risk-assessment-history.dto.d.ts +28 -0
- package/dist/dto/get-risk-assessment-history.dto.d.ts.map +1 -1
- package/dist/dto/get-risk-assessment-history.dto.js +28 -0
- package/dist/dto/get-risk-assessment-history.dto.js.map +1 -1
- package/dist/dto/get-session-id-response.dto.d.ts +21 -0
- package/dist/dto/get-session-id-response.dto.d.ts.map +1 -1
- package/dist/dto/get-session-id-response.dto.js +21 -0
- package/dist/dto/get-session-id-response.dto.js.map +1 -1
- package/dist/dto/get-setup-data-response.dto.d.ts +27 -0
- package/dist/dto/get-setup-data-response.dto.d.ts.map +1 -1
- package/dist/dto/get-setup-data-response.dto.js +27 -0
- package/dist/dto/get-setup-data-response.dto.js.map +1 -1
- package/dist/dto/get-setup-data.dto.d.ts +51 -0
- package/dist/dto/get-setup-data.dto.d.ts.map +1 -1
- package/dist/dto/get-setup-data.dto.js +51 -0
- package/dist/dto/get-setup-data.dto.js.map +1 -1
- package/dist/dto/get-suspicious-activity.dto.d.ts +31 -0
- package/dist/dto/get-suspicious-activity.dto.d.ts.map +1 -1
- package/dist/dto/get-suspicious-activity.dto.js +31 -0
- package/dist/dto/get-suspicious-activity.dto.js.map +1 -1
- package/dist/dto/get-user-agent-response.dto.d.ts +19 -0
- package/dist/dto/get-user-agent-response.dto.d.ts.map +1 -1
- package/dist/dto/get-user-agent-response.dto.js +19 -0
- package/dist/dto/get-user-agent-response.dto.js.map +1 -1
- package/dist/dto/get-user-auth-history.dto.d.ts +64 -0
- package/dist/dto/get-user-auth-history.dto.d.ts.map +1 -1
- package/dist/dto/get-user-auth-history.dto.js +64 -0
- package/dist/dto/get-user-auth-history.dto.js.map +1 -1
- package/dist/dto/get-user-by-email.dto.d.ts +42 -0
- package/dist/dto/get-user-by-email.dto.d.ts.map +1 -1
- package/dist/dto/get-user-by-email.dto.js +42 -0
- package/dist/dto/get-user-by-email.dto.js.map +1 -1
- package/dist/dto/get-user-by-id.dto.d.ts +32 -0
- package/dist/dto/get-user-by-id.dto.d.ts.map +1 -1
- package/dist/dto/get-user-by-id.dto.js +32 -0
- package/dist/dto/get-user-by-id.dto.js.map +1 -1
- package/dist/dto/get-user-devices.dto.d.ts +34 -0
- package/dist/dto/get-user-devices.dto.d.ts.map +1 -1
- package/dist/dto/get-user-devices.dto.js +34 -0
- package/dist/dto/get-user-devices.dto.js.map +1 -1
- package/dist/dto/get-user-response.dto.d.ts +14 -0
- package/dist/dto/get-user-response.dto.d.ts.map +1 -1
- package/dist/dto/get-user-response.dto.js +15 -0
- package/dist/dto/get-user-response.dto.js.map +1 -1
- package/dist/dto/has-provider.dto.d.ts +33 -0
- package/dist/dto/has-provider.dto.d.ts.map +1 -1
- package/dist/dto/has-provider.dto.js +33 -0
- package/dist/dto/has-provider.dto.js.map +1 -1
- package/dist/dto/index.js +5 -0
- package/dist/dto/index.js.map +1 -1
- package/dist/dto/is-trusted-device-response.dto.d.ts +28 -0
- package/dist/dto/is-trusted-device-response.dto.d.ts.map +1 -1
- package/dist/dto/is-trusted-device-response.dto.js +28 -0
- package/dist/dto/is-trusted-device-response.dto.js.map +1 -1
- package/dist/dto/list-providers-response.dto.d.ts +19 -0
- package/dist/dto/list-providers-response.dto.d.ts.map +1 -1
- package/dist/dto/list-providers-response.dto.js +19 -0
- package/dist/dto/list-providers-response.dto.js.map +1 -1
- package/dist/dto/login.dto.d.ts +48 -0
- package/dist/dto/login.dto.d.ts.map +1 -1
- package/dist/dto/login.dto.js +50 -1
- package/dist/dto/login.dto.js.map +1 -1
- package/dist/dto/logout-all-response.dto.d.ts +20 -0
- package/dist/dto/logout-all-response.dto.d.ts.map +1 -1
- package/dist/dto/logout-all-response.dto.js +20 -0
- package/dist/dto/logout-all-response.dto.js.map +1 -1
- package/dist/dto/logout-all.dto.d.ts +42 -0
- package/dist/dto/logout-all.dto.d.ts.map +1 -1
- package/dist/dto/logout-all.dto.js +42 -0
- package/dist/dto/logout-all.dto.js.map +1 -1
- package/dist/dto/logout-response.dto.d.ts +21 -0
- package/dist/dto/logout-response.dto.d.ts.map +1 -1
- package/dist/dto/logout-response.dto.js +21 -0
- package/dist/dto/logout-response.dto.js.map +1 -1
- package/dist/dto/logout.dto.d.ts +45 -0
- package/dist/dto/logout.dto.d.ts.map +1 -1
- package/dist/dto/logout.dto.js +45 -0
- package/dist/dto/logout.dto.js.map +1 -1
- package/dist/dto/refresh-token.dto.d.ts +28 -0
- package/dist/dto/refresh-token.dto.d.ts.map +1 -1
- package/dist/dto/refresh-token.dto.js +28 -0
- package/dist/dto/refresh-token.dto.js.map +1 -1
- package/dist/dto/remove-devices.dto.d.ts +51 -0
- package/dist/dto/remove-devices.dto.d.ts.map +1 -1
- package/dist/dto/remove-devices.dto.js +51 -0
- package/dist/dto/remove-devices.dto.js.map +1 -1
- package/dist/dto/resend-code-response.dto.d.ts +28 -0
- package/dist/dto/resend-code-response.dto.d.ts.map +1 -1
- package/dist/dto/resend-code-response.dto.js +28 -0
- package/dist/dto/resend-code-response.dto.js.map +1 -1
- package/dist/dto/resend-code.dto.d.ts +37 -0
- package/dist/dto/resend-code.dto.d.ts.map +1 -1
- package/dist/dto/resend-code.dto.js +37 -0
- package/dist/dto/resend-code.dto.js.map +1 -1
- package/dist/dto/reset-password.dto.d.ts +74 -0
- package/dist/dto/reset-password.dto.d.ts.map +1 -1
- package/dist/dto/reset-password.dto.js +76 -1
- package/dist/dto/reset-password.dto.js.map +1 -1
- package/dist/dto/respond-challenge.dto.d.ts +147 -0
- package/dist/dto/respond-challenge.dto.d.ts.map +1 -1
- package/dist/dto/respond-challenge.dto.js +162 -0
- package/dist/dto/respond-challenge.dto.js.map +1 -1
- package/dist/dto/set-mfa-exemption.dto.d.ts +65 -0
- package/dist/dto/set-mfa-exemption.dto.d.ts.map +1 -1
- package/dist/dto/set-mfa-exemption.dto.js +65 -0
- package/dist/dto/set-mfa-exemption.dto.js.map +1 -1
- package/dist/dto/set-must-change-password-response.dto.d.ts +23 -0
- package/dist/dto/set-must-change-password-response.dto.d.ts.map +1 -1
- package/dist/dto/set-must-change-password-response.dto.js +23 -0
- package/dist/dto/set-must-change-password-response.dto.js.map +1 -1
- package/dist/dto/set-must-change-password.dto.d.ts +32 -0
- package/dist/dto/set-must-change-password.dto.d.ts.map +1 -1
- package/dist/dto/set-must-change-password.dto.js +32 -0
- package/dist/dto/set-must-change-password.dto.js.map +1 -1
- package/dist/dto/set-preferred-method.dto.d.ts +48 -0
- package/dist/dto/set-preferred-method.dto.d.ts.map +1 -1
- package/dist/dto/set-preferred-method.dto.js +48 -0
- package/dist/dto/set-preferred-method.dto.js.map +1 -1
- package/dist/dto/setup-mfa.dto.d.ts +62 -0
- package/dist/dto/setup-mfa.dto.d.ts.map +1 -1
- package/dist/dto/setup-mfa.dto.js +62 -0
- package/dist/dto/setup-mfa.dto.js.map +1 -1
- package/dist/dto/signup.dto.d.ts +92 -0
- package/dist/dto/signup.dto.d.ts.map +1 -1
- package/dist/dto/signup.dto.js +93 -0
- package/dist/dto/signup.dto.js.map +1 -1
- package/dist/dto/social-auth.dto.d.ts +234 -0
- package/dist/dto/social-auth.dto.d.ts.map +1 -1
- package/dist/dto/social-auth.dto.js +234 -0
- package/dist/dto/social-auth.dto.js.map +1 -1
- package/dist/dto/trust-device-response.dto.d.ts +26 -0
- package/dist/dto/trust-device-response.dto.d.ts.map +1 -1
- package/dist/dto/trust-device-response.dto.js +26 -0
- package/dist/dto/trust-device-response.dto.js.map +1 -1
- package/dist/dto/trust-device.dto.d.ts +9 -0
- package/dist/dto/trust-device.dto.d.ts.map +1 -1
- package/dist/dto/trust-device.dto.js +9 -0
- package/dist/dto/trust-device.dto.js.map +1 -1
- package/dist/dto/update-user-attributes-request.dto.d.ts +36 -0
- package/dist/dto/update-user-attributes-request.dto.d.ts.map +1 -1
- package/dist/dto/update-user-attributes-request.dto.js +36 -0
- package/dist/dto/update-user-attributes-request.dto.js.map +1 -1
- package/dist/dto/user-response.dto.d.ts +81 -0
- package/dist/dto/user-response.dto.d.ts.map +1 -1
- package/dist/dto/user-response.dto.js +84 -2
- package/dist/dto/user-response.dto.js.map +1 -1
- package/dist/dto/user-update.dto.d.ts +132 -0
- package/dist/dto/user-update.dto.d.ts.map +1 -1
- package/dist/dto/user-update.dto.js +133 -0
- package/dist/dto/user-update.dto.js.map +1 -1
- package/dist/dto/verify-email.dto.d.ts +171 -0
- package/dist/dto/verify-email.dto.d.ts.map +1 -1
- package/dist/dto/verify-email.dto.js +173 -1
- package/dist/dto/verify-email.dto.js.map +1 -1
- package/dist/dto/verify-mfa-code.dto.d.ts +65 -0
- package/dist/dto/verify-mfa-code.dto.d.ts.map +1 -1
- package/dist/dto/verify-mfa-code.dto.js +65 -0
- package/dist/dto/verify-mfa-code.dto.js.map +1 -1
- package/dist/dto/verify-phone-by-sub.dto.d.ts +49 -0
- package/dist/dto/verify-phone-by-sub.dto.d.ts.map +1 -1
- package/dist/dto/verify-phone-by-sub.dto.js +49 -0
- package/dist/dto/verify-phone-by-sub.dto.js.map +1 -1
- package/dist/dto/verify-phone.dto.d.ts +139 -0
- package/dist/dto/verify-phone.dto.d.ts.map +1 -1
- package/dist/dto/verify-phone.dto.js +142 -1
- package/dist/dto/verify-phone.dto.js.map +1 -1
- package/dist/dto.d.ts +10 -0
- package/dist/dto.d.ts.map +1 -1
- package/dist/dto.js +10 -0
- package/dist/dto.js.map +1 -1
- package/dist/entities/auth-audit.entity.d.ts +159 -0
- package/dist/entities/auth-audit.entity.d.ts.map +1 -1
- package/dist/entities/auth-audit.entity.js +166 -0
- package/dist/entities/auth-audit.entity.js.map +1 -1
- package/dist/entities/challenge-session.entity.d.ts +87 -0
- package/dist/entities/challenge-session.entity.d.ts.map +1 -1
- package/dist/entities/challenge-session.entity.js +87 -0
- package/dist/entities/challenge-session.entity.js.map +1 -1
- package/dist/entities/index.d.ts +18 -0
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +18 -0
- package/dist/entities/index.js.map +1 -1
- package/dist/entities/login-attempt.entity.d.ts +43 -0
- package/dist/entities/login-attempt.entity.d.ts.map +1 -1
- package/dist/entities/login-attempt.entity.js +43 -0
- package/dist/entities/login-attempt.entity.js.map +1 -1
- package/dist/entities/mfa-device.entity.d.ts +112 -0
- package/dist/entities/mfa-device.entity.d.ts.map +1 -1
- package/dist/entities/mfa-device.entity.js +112 -0
- package/dist/entities/mfa-device.entity.js.map +1 -1
- package/dist/entities/rate-limit.entity.d.ts +31 -0
- package/dist/entities/rate-limit.entity.d.ts.map +1 -1
- package/dist/entities/rate-limit.entity.js +31 -0
- package/dist/entities/rate-limit.entity.js.map +1 -1
- package/dist/entities/session.entity.d.ts +121 -0
- package/dist/entities/session.entity.d.ts.map +1 -1
- package/dist/entities/session.entity.js +121 -0
- package/dist/entities/session.entity.js.map +1 -1
- package/dist/entities/social-account.entity.d.ts +75 -0
- package/dist/entities/social-account.entity.d.ts.map +1 -1
- package/dist/entities/social-account.entity.js +75 -0
- package/dist/entities/social-account.entity.js.map +1 -1
- package/dist/entities/storage-lock.entity.d.ts +28 -0
- package/dist/entities/storage-lock.entity.d.ts.map +1 -1
- package/dist/entities/storage-lock.entity.js +28 -0
- package/dist/entities/storage-lock.entity.js.map +1 -1
- package/dist/entities/trusted-device.entity.d.ts +83 -0
- package/dist/entities/trusted-device.entity.d.ts.map +1 -1
- package/dist/entities/trusted-device.entity.js +83 -0
- package/dist/entities/trusted-device.entity.js.map +1 -1
- package/dist/entities/user.entity.d.ts +166 -0
- package/dist/entities/user.entity.d.ts.map +1 -1
- package/dist/entities/user.entity.js +166 -0
- package/dist/entities/user.entity.js.map +1 -1
- package/dist/entities/verification-token.entity.d.ts +102 -0
- package/dist/entities/verification-token.entity.d.ts.map +1 -1
- package/dist/entities/verification-token.entity.js +102 -0
- package/dist/entities/verification-token.entity.js.map +1 -1
- package/dist/entities.d.ts +8 -0
- package/dist/entities.d.ts.map +1 -1
- package/dist/entities.js +8 -0
- package/dist/entities.js.map +1 -1
- package/dist/enums/auth-audit-event-type.enum.d.ts +211 -0
- package/dist/enums/auth-audit-event-type.enum.d.ts.map +1 -1
- package/dist/enums/auth-audit-event-type.enum.js +244 -0
- package/dist/enums/auth-audit-event-type.enum.js.map +1 -1
- package/dist/enums/error-codes.enum.d.ts +296 -0
- package/dist/enums/error-codes.enum.d.ts.map +1 -1
- package/dist/enums/error-codes.enum.js +332 -0
- package/dist/enums/error-codes.enum.js.map +1 -1
- package/dist/enums/mfa-method.enum.d.ts +74 -0
- package/dist/enums/mfa-method.enum.d.ts.map +1 -1
- package/dist/enums/mfa-method.enum.js +64 -0
- package/dist/enums/mfa-method.enum.js.map +1 -1
- package/dist/enums/risk-factor.enum.d.ts +91 -0
- package/dist/enums/risk-factor.enum.d.ts.map +1 -1
- package/dist/enums/risk-factor.enum.js +97 -0
- package/dist/enums/risk-factor.enum.js.map +1 -1
- package/dist/exceptions/nauth.exception.d.ts +149 -0
- package/dist/exceptions/nauth.exception.d.ts.map +1 -1
- package/dist/exceptions/nauth.exception.js +159 -0
- package/dist/exceptions/nauth.exception.js.map +1 -1
- package/dist/handlers/auth.handler.d.ts +32 -0
- package/dist/handlers/auth.handler.d.ts.map +1 -1
- package/dist/handlers/auth.handler.js +47 -1
- package/dist/handlers/auth.handler.js.map +1 -1
- package/dist/handlers/client-info.handler.d.ts +25 -0
- package/dist/handlers/client-info.handler.d.ts.map +1 -1
- package/dist/handlers/client-info.handler.js +36 -2
- package/dist/handlers/client-info.handler.js.map +1 -1
- package/dist/handlers/csrf.handler.d.ts +32 -0
- package/dist/handlers/csrf.handler.d.ts.map +1 -1
- package/dist/handlers/csrf.handler.js +49 -1
- package/dist/handlers/csrf.handler.js.map +1 -1
- package/dist/handlers/token-delivery.handler.d.ts +16 -0
- package/dist/handlers/token-delivery.handler.d.ts.map +1 -1
- package/dist/handlers/token-delivery.handler.js +22 -1
- package/dist/handlers/token-delivery.handler.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/client-info.interface.d.ts +58 -0
- package/dist/interfaces/client-info.interface.d.ts.map +1 -1
- package/dist/interfaces/config.interface.d.ts +1774 -0
- package/dist/interfaces/config.interface.d.ts.map +1 -1
- package/dist/interfaces/config.interface.js +16 -0
- package/dist/interfaces/config.interface.js.map +1 -1
- package/dist/interfaces/entities.interface.d.ts +48 -0
- package/dist/interfaces/entities.interface.d.ts.map +1 -1
- package/dist/interfaces/entities.interface.js +8 -0
- package/dist/interfaces/entities.interface.js.map +1 -1
- package/dist/interfaces/index.js +5 -0
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/logger.interface.d.ts +213 -0
- package/dist/interfaces/logger.interface.d.ts.map +1 -1
- package/dist/interfaces/logger.interface.js +35 -0
- package/dist/interfaces/logger.interface.js.map +1 -1
- package/dist/interfaces/mfa-provider.interface.d.ts +134 -0
- package/dist/interfaces/mfa-provider.interface.d.ts.map +1 -1
- package/dist/interfaces/oauth.interface.d.ts +110 -0
- package/dist/interfaces/oauth.interface.d.ts.map +1 -1
- package/dist/interfaces/provider.interface.d.ts +83 -0
- package/dist/interfaces/provider.interface.d.ts.map +1 -1
- package/dist/interfaces/sms-template.interface.d.ts +246 -0
- package/dist/interfaces/sms-template.interface.d.ts.map +1 -1
- package/dist/interfaces/sms-template.interface.js +26 -0
- package/dist/interfaces/sms-template.interface.js.map +1 -1
- package/dist/interfaces/social-auth-provider.interface.d.ts +115 -0
- package/dist/interfaces/social-auth-provider.interface.d.ts.map +1 -1
- package/dist/interfaces/storage-adapter.interface.d.ts +37 -0
- package/dist/interfaces/storage-adapter.interface.d.ts.map +1 -1
- package/dist/interfaces/template.interface.d.ts +351 -0
- package/dist/interfaces/template.interface.d.ts.map +1 -1
- package/dist/interfaces/template.interface.js +13 -0
- package/dist/interfaces/template.interface.js.map +1 -1
- package/dist/interfaces/token-verifier.interface.d.ts +101 -0
- package/dist/interfaces/token-verifier.interface.d.ts.map +1 -1
- package/dist/interfaces.d.ts +8 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js +8 -0
- package/dist/interfaces.js.map +1 -1
- package/dist/internal.d.ts +120 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +138 -0
- package/dist/internal.js.map +1 -1
- package/dist/platform/interfaces.d.ts +187 -0
- package/dist/platform/interfaces.d.ts.map +1 -1
- package/dist/platform/interfaces.js +11 -0
- package/dist/platform/interfaces.js.map +1 -1
- package/dist/schemas/auth-config.schema.d.ts +48 -0
- package/dist/schemas/auth-config.schema.d.ts.map +1 -1
- package/dist/schemas/auth-config.schema.js +188 -9
- package/dist/schemas/auth-config.schema.js.map +1 -1
- package/dist/services/adaptive-mfa-decision.service.d.ts +144 -0
- package/dist/services/adaptive-mfa-decision.service.d.ts.map +1 -1
- package/dist/services/adaptive-mfa-decision.service.js +151 -5
- package/dist/services/adaptive-mfa-decision.service.js.map +1 -1
- package/dist/services/auth-audit.service.d.ts +195 -0
- package/dist/services/auth-audit.service.d.ts.map +1 -1
- package/dist/services/auth-audit.service.js +228 -1
- package/dist/services/auth-audit.service.js.map +1 -1
- package/dist/services/auth-challenge-helper.service.d.ts +144 -1
- package/dist/services/auth-challenge-helper.service.d.ts.map +1 -1
- package/dist/services/auth-challenge-helper.service.js +295 -16
- package/dist/services/auth-challenge-helper.service.js.map +1 -1
- package/dist/services/auth-flow-context-builder.service.d.ts +120 -1
- package/dist/services/auth-flow-context-builder.service.d.ts.map +1 -1
- package/dist/services/auth-flow-context-builder.service.js +184 -5
- package/dist/services/auth-flow-context-builder.service.js.map +1 -1
- package/dist/services/auth-flow-rules.d.ts +136 -0
- package/dist/services/auth-flow-rules.d.ts.map +1 -1
- package/dist/services/auth-flow-rules.js +137 -0
- package/dist/services/auth-flow-rules.js.map +1 -1
- package/dist/services/auth-flow-state-definitions.d.ts +40 -0
- package/dist/services/auth-flow-state-definitions.d.ts.map +1 -1
- package/dist/services/auth-flow-state-definitions.js +98 -0
- package/dist/services/auth-flow-state-definitions.js.map +1 -1
- package/dist/services/auth-flow-state-machine.service.d.ts +91 -0
- package/dist/services/auth-flow-state-machine.service.d.ts.map +1 -1
- package/dist/services/auth-flow-state-machine.service.js +102 -0
- package/dist/services/auth-flow-state-machine.service.js.map +1 -1
- package/dist/services/auth-flow-state-machine.types.d.ts +221 -0
- package/dist/services/auth-flow-state-machine.types.d.ts.map +1 -1
- package/dist/services/auth-flow-state-machine.types.js +47 -0
- package/dist/services/auth-flow-state-machine.types.js.map +1 -1
- package/dist/services/auth.service.d.ts +397 -1
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +943 -27
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/challenge.service.d.ts +255 -1
- package/dist/services/challenge.service.d.ts.map +1 -1
- package/dist/services/challenge.service.js +327 -3
- package/dist/services/challenge.service.js.map +1 -1
- package/dist/services/client-info.service.d.ts +143 -0
- package/dist/services/client-info.service.d.ts.map +1 -1
- package/dist/services/client-info.service.js +161 -0
- package/dist/services/client-info.service.js.map +1 -1
- package/dist/services/csrf.service.d.ts +15 -0
- package/dist/services/csrf.service.d.ts.map +1 -1
- package/dist/services/csrf.service.js +16 -0
- package/dist/services/csrf.service.js.map +1 -1
- package/dist/services/email-verification.service.d.ts +52 -0
- package/dist/services/email-verification.service.d.ts.map +1 -1
- package/dist/services/email-verification.service.js +149 -10
- package/dist/services/email-verification.service.js.map +1 -1
- package/dist/services/geo-location.service.d.ts +105 -0
- package/dist/services/geo-location.service.d.ts.map +1 -1
- package/dist/services/geo-location.service.js +188 -2
- package/dist/services/geo-location.service.js.map +1 -1
- package/dist/services/jwt.service.d.ts +257 -0
- package/dist/services/jwt.service.d.ts.map +1 -1
- package/dist/services/jwt.service.js +284 -1
- package/dist/services/jwt.service.js.map +1 -1
- package/dist/services/mfa-base.service.d.ts +179 -1
- package/dist/services/mfa-base.service.d.ts.map +1 -1
- package/dist/services/mfa-base.service.js +256 -2
- package/dist/services/mfa-base.service.js.map +1 -1
- package/dist/services/mfa.service.d.ts +304 -0
- package/dist/services/mfa.service.d.ts.map +1 -1
- package/dist/services/mfa.service.js +380 -0
- package/dist/services/mfa.service.js.map +1 -1
- package/dist/services/password-reset.service.d.ts +46 -0
- package/dist/services/password-reset.service.d.ts.map +1 -1
- package/dist/services/password-reset.service.js +79 -0
- package/dist/services/password-reset.service.js.map +1 -1
- package/dist/services/password.service.d.ts +139 -0
- package/dist/services/password.service.d.ts.map +1 -1
- package/dist/services/password.service.js +167 -9
- package/dist/services/password.service.js.map +1 -1
- package/dist/services/phone-verification.service.d.ts +75 -0
- package/dist/services/phone-verification.service.d.ts.map +1 -1
- package/dist/services/phone-verification.service.js +188 -6
- package/dist/services/phone-verification.service.js.map +1 -1
- package/dist/services/risk-detection.service.d.ts +198 -0
- package/dist/services/risk-detection.service.d.ts.map +1 -1
- package/dist/services/risk-detection.service.js +358 -11
- package/dist/services/risk-detection.service.js.map +1 -1
- package/dist/services/risk-scoring.service.d.ts +84 -0
- package/dist/services/risk-scoring.service.d.ts.map +1 -1
- package/dist/services/risk-scoring.service.js +87 -0
- package/dist/services/risk-scoring.service.js.map +1 -1
- package/dist/services/session.service.d.ts +204 -0
- package/dist/services/session.service.d.ts.map +1 -1
- package/dist/services/session.service.js +289 -4
- package/dist/services/session.service.js.map +1 -1
- package/dist/services/social-auth-base.service.d.ts +123 -1
- package/dist/services/social-auth-base.service.d.ts.map +1 -1
- package/dist/services/social-auth-base.service.js +155 -2
- package/dist/services/social-auth-base.service.js.map +1 -1
- package/dist/services/social-auth.service.d.ts +191 -0
- package/dist/services/social-auth.service.d.ts.map +1 -1
- package/dist/services/social-auth.service.js +215 -2
- package/dist/services/social-auth.service.js.map +1 -1
- package/dist/services/social-provider-registry.service.d.ts +86 -0
- package/dist/services/social-provider-registry.service.d.ts.map +1 -1
- package/dist/services/social-provider-registry.service.js +86 -0
- package/dist/services/social-provider-registry.service.js.map +1 -1
- package/dist/services/trusted-device.service.d.ts +105 -0
- package/dist/services/trusted-device.service.d.ts.map +1 -1
- package/dist/services/trusted-device.service.js +133 -4
- package/dist/services/trusted-device.service.js.map +1 -1
- package/dist/storage/account-lockout-storage.service.d.ts +35 -0
- package/dist/storage/account-lockout-storage.service.d.ts.map +1 -1
- package/dist/storage/account-lockout-storage.service.js +35 -0
- package/dist/storage/account-lockout-storage.service.js.map +1 -1
- package/dist/storage/memory-storage.adapter.d.ts +148 -0
- package/dist/storage/memory-storage.adapter.d.ts.map +1 -1
- package/dist/storage/memory-storage.adapter.js +201 -6
- package/dist/storage/memory-storage.adapter.js.map +1 -1
- package/dist/storage/rate-limit-storage.service.d.ts +3 -0
- package/dist/storage/rate-limit-storage.service.d.ts.map +1 -1
- package/dist/storage/rate-limit-storage.service.js +4 -0
- package/dist/storage/rate-limit-storage.service.js.map +1 -1
- package/dist/storage.d.ts +8 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +8 -0
- package/dist/storage.js.map +1 -1
- package/dist/templates/html-template.engine.d.ts +110 -0
- package/dist/templates/html-template.engine.d.ts.map +1 -1
- package/dist/templates/html-template.engine.js +147 -0
- package/dist/templates/html-template.engine.js.map +1 -1
- package/dist/templates/index.d.ts +5 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +5 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/sms-template.engine.d.ts +151 -0
- package/dist/templates/sms-template.engine.d.ts.map +1 -1
- package/dist/templates/sms-template.engine.js +171 -0
- package/dist/templates/sms-template.engine.js.map +1 -1
- package/dist/templates.d.ts +8 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +8 -0
- package/dist/templates.js.map +1 -1
- package/dist/utils/common-passwords.d.ts +42 -0
- package/dist/utils/common-passwords.d.ts.map +1 -1
- package/dist/utils/common-passwords.js +88 -0
- package/dist/utils/common-passwords.js.map +1 -1
- package/dist/utils/context-storage.d.ts +129 -0
- package/dist/utils/context-storage.d.ts.map +1 -1
- package/dist/utils/context-storage.js +129 -0
- package/dist/utils/context-storage.js.map +1 -1
- package/dist/utils/cookie-names.util.d.ts +35 -0
- package/dist/utils/cookie-names.util.d.ts.map +1 -1
- package/dist/utils/cookie-names.util.js +37 -0
- package/dist/utils/cookie-names.util.js.map +1 -1
- package/dist/utils/cookies.util.d.ts +19 -0
- package/dist/utils/cookies.util.d.ts.map +1 -1
- package/dist/utils/cookies.util.js +30 -3
- package/dist/utils/cookies.util.js.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/ip-extractor.d.ts +88 -0
- package/dist/utils/ip-extractor.d.ts.map +1 -1
- package/dist/utils/ip-extractor.js +109 -16
- package/dist/utils/ip-extractor.js.map +1 -1
- package/dist/utils/nauth-logger.d.ts +70 -0
- package/dist/utils/nauth-logger.d.ts.map +1 -1
- package/dist/utils/nauth-logger.js +82 -4
- package/dist/utils/nauth-logger.js.map +1 -1
- package/dist/utils/pii-redactor.d.ts +70 -0
- package/dist/utils/pii-redactor.d.ts.map +1 -1
- package/dist/utils/pii-redactor.js +102 -0
- package/dist/utils/pii-redactor.js.map +1 -1
- package/dist/utils/setup/get-repositories.d.ts +16 -0
- package/dist/utils/setup/get-repositories.d.ts.map +1 -1
- package/dist/utils/setup/get-repositories.js +21 -0
- package/dist/utils/setup/get-repositories.js.map +1 -1
- package/dist/utils/setup/init-services.d.ts +40 -1
- package/dist/utils/setup/init-services.d.ts.map +1 -1
- package/dist/utils/setup/init-services.js +98 -0
- package/dist/utils/setup/init-services.js.map +1 -1
- package/dist/utils/setup/init-social.d.ts +27 -0
- package/dist/utils/setup/init-social.d.ts.map +1 -1
- package/dist/utils/setup/init-social.js +49 -0
- package/dist/utils/setup/init-social.js.map +1 -1
- package/dist/utils/setup/init-storage.d.ts +22 -0
- package/dist/utils/setup/init-storage.d.ts.map +1 -1
- package/dist/utils/setup/init-storage.js +36 -0
- package/dist/utils/setup/init-storage.js.map +1 -1
- package/dist/utils/setup/register-mfa.d.ts +22 -0
- package/dist/utils/setup/register-mfa.d.ts.map +1 -1
- package/dist/utils/setup/register-mfa.js +41 -0
- package/dist/utils/setup/register-mfa.js.map +1 -1
- package/dist/utils/setup/run-nauth-migrations.d.ts +7 -0
- package/dist/utils/setup/run-nauth-migrations.d.ts.map +1 -1
- package/dist/utils/setup/run-nauth-migrations.js +8 -0
- package/dist/utils/setup/run-nauth-migrations.js.map +1 -1
- package/dist/utils/token-delivery-policy.d.ts +17 -0
- package/dist/utils/token-delivery-policy.d.ts.map +1 -1
- package/dist/utils/token-delivery-policy.js +17 -0
- package/dist/utils/token-delivery-policy.js.map +1 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +8 -0
- package/dist/utils.js.map +1 -1
- package/dist/validators/template.validator.d.ts +80 -0
- package/dist/validators/template.validator.d.ts.map +1 -1
- package/dist/validators/template.validator.js +94 -0
- package/dist/validators/template.validator.js.map +1 -1
- package/package.json +7 -2
|
@@ -4,6 +4,31 @@ exports.RiskDetectionService = void 0;
|
|
|
4
4
|
const typeorm_1 = require("typeorm");
|
|
5
5
|
const auth_audit_event_type_enum_1 = require("../enums/auth-audit-event-type.enum");
|
|
6
6
|
const risk_factor_enum_1 = require("../enums/risk-factor.enum");
|
|
7
|
+
/**
|
|
8
|
+
* Risk Detection Service
|
|
9
|
+
*
|
|
10
|
+
* Analyzes authentication attempts for risk factors by comparing current
|
|
11
|
+
* context against user's historical behavior (sessions and audit trail).
|
|
12
|
+
*
|
|
13
|
+
* **Risk Factors Detected:**
|
|
14
|
+
* - `new_device`: DeviceId never seen before (check sessions table)
|
|
15
|
+
* - `new_ip`: IP address never seen before (check sessions + audit)
|
|
16
|
+
* - `new_country`: Country never seen before (check sessions where ipCountry)
|
|
17
|
+
* - `impossible_travel`: Geographic distance impossible in time window
|
|
18
|
+
* - `suspicious_activity`: Recent failed attempts, token reuse, etc.
|
|
19
|
+
*
|
|
20
|
+
* **Design Notes:**
|
|
21
|
+
* - All queries use userId (internal integer ID) for optimal performance
|
|
22
|
+
* - Queries are optimized with COUNT and LIMIT 1 for existence checks
|
|
23
|
+
* - Non-blocking: Errors logged but don't throw (graceful degradation)
|
|
24
|
+
* - Impossible travel detection requires city-level geolocation (optional)
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const riskFactors = await riskDetectionService.detectRiskFactors(user, clientInfo);
|
|
29
|
+
* // Returns: ['new_device', 'new_country']
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
7
32
|
class RiskDetectionService {
|
|
8
33
|
sessionRepository;
|
|
9
34
|
auditRepository;
|
|
@@ -17,9 +42,35 @@ class RiskDetectionService {
|
|
|
17
42
|
this.logger = logger;
|
|
18
43
|
this.trustedDeviceService = trustedDeviceService;
|
|
19
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Detect risk factors for current authentication attempt
|
|
47
|
+
*
|
|
48
|
+
* Compares current context against user's historical behavior to identify
|
|
49
|
+
* potential security risks. Returns array of detected risk factor strings.
|
|
50
|
+
*
|
|
51
|
+
* **Double-Counting Prevention:**
|
|
52
|
+
* - If `new_country` is detected, `new_ip` is NOT checked (IP is source of country data)
|
|
53
|
+
* - If `impossible_travel` is detected (city change), `new_ip` is NOT checked
|
|
54
|
+
* - This prevents double-counting the same underlying risk (location change)
|
|
55
|
+
*
|
|
56
|
+
* @param user - User being authenticated
|
|
57
|
+
* @param clientInfo - Current request context (IP, device, location, etc.)
|
|
58
|
+
* @returns Array of detected risk factor strings
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const factors = await riskDetectionService.detectRiskFactors(user, clientInfo);
|
|
63
|
+
* // Returns: ['new_device', 'new_country'] // new_ip excluded if new_country detected
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
20
66
|
async detectRiskFactors(user, clientInfo) {
|
|
21
67
|
const factors = [];
|
|
22
68
|
try {
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Trigger Configuration
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Check which triggers are enabled in config
|
|
73
|
+
// Config uses string literals, but they match RiskFactor enum values
|
|
23
74
|
const enabledTriggers = this.config.mfa?.adaptive?.triggers || [
|
|
24
75
|
risk_factor_enum_1.RiskFactor.NEW_DEVICE,
|
|
25
76
|
risk_factor_enum_1.RiskFactor.NEW_IP,
|
|
@@ -27,8 +78,14 @@ class RiskDetectionService {
|
|
|
27
78
|
risk_factor_enum_1.RiskFactor.RECENT_PASSWORD_RESET,
|
|
28
79
|
];
|
|
29
80
|
this.logger?.debug?.(`Risk detection started for user ${user.sub}: enabled_triggers=[${enabledTriggers.join(', ')}], has_device_token=${!!clientInfo.deviceToken}, ip=${clientInfo.ipAddress}, location=${clientInfo.ipCity}, ${clientInfo.ipCountry}`);
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Device Risk Detection
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Check new_device
|
|
30
85
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.NEW_DEVICE)) {
|
|
31
86
|
if (clientInfo.deviceToken) {
|
|
87
|
+
// Device has a token - check if it's been seen before
|
|
88
|
+
// This is the most reliable method (cookie-based or header-based token)
|
|
32
89
|
const isNew = await this.isNewDevice(user.id, clientInfo.deviceToken);
|
|
33
90
|
if (isNew) {
|
|
34
91
|
factors.push(risk_factor_enum_1.RiskFactor.NEW_DEVICE);
|
|
@@ -36,13 +93,24 @@ class RiskDetectionService {
|
|
|
36
93
|
}
|
|
37
94
|
}
|
|
38
95
|
else {
|
|
96
|
+
// No deviceToken (incognito mode, cleared cookies, first login, etc.)
|
|
97
|
+
// Check if user has logged in before - if yes, missing token is suspicious
|
|
98
|
+
// This prevents false positives for legitimate first-time logins
|
|
39
99
|
const hasPreviousSessions = await this.hasUserLoggedInBefore(user.id);
|
|
40
100
|
if (hasPreviousSessions) {
|
|
101
|
+
// User has logged in before but no deviceToken - treat as new/unknown device
|
|
102
|
+
// This covers incognito mode, cleared cookies, and other scenarios where
|
|
103
|
+
// device identification is not available
|
|
41
104
|
factors.push(risk_factor_enum_1.RiskFactor.NEW_DEVICE);
|
|
42
105
|
this.logger?.debug?.(`Missing deviceToken for user ${user.sub} with previous sessions - treating as new_device`);
|
|
43
106
|
}
|
|
44
107
|
}
|
|
45
108
|
}
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// Location Risk Detection (with Double-Counting Prevention)
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Check new_country first (before new_ip to avoid double-counting)
|
|
113
|
+
// IP is the source of country/city data, so if country changed, IP definitely changed
|
|
46
114
|
let newCountryDetected = false;
|
|
47
115
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.NEW_COUNTRY) && clientInfo.ipCountry) {
|
|
48
116
|
const isNew = await this.isNewCountry(user.id, clientInfo.ipCountry);
|
|
@@ -52,6 +120,8 @@ class RiskDetectionService {
|
|
|
52
120
|
this.logger?.debug?.(`New country detected for user ${user.sub}: ${clientInfo.ipCountry}`);
|
|
53
121
|
}
|
|
54
122
|
}
|
|
123
|
+
// Check impossible_travel (now works with country-only data too)
|
|
124
|
+
// This also indicates location change (city/country), so skip new_ip if detected
|
|
55
125
|
let impossibleTravelDetected = false;
|
|
56
126
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.IMPOSSIBLE_TRAVEL) && clientInfo.ipCountry) {
|
|
57
127
|
const isImpossible = await this.detectImpossibleTravel(user.id, clientInfo);
|
|
@@ -61,6 +131,13 @@ class RiskDetectionService {
|
|
|
61
131
|
this.logger?.debug?.(`Impossible travel detected for user ${user.sub}: ${clientInfo.ipCity ?? 'unknown'}, ${clientInfo.ipCountry}`);
|
|
62
132
|
}
|
|
63
133
|
}
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Location Data Completeness Check
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Add risk factor if location data is incomplete (missing city or coordinates) when country exists
|
|
138
|
+
// AND user has previous logins (only relevant for returning users)
|
|
139
|
+
// This reduces confidence in risk assessment and warrants extra caution
|
|
140
|
+
// Only check if location-related triggers are enabled
|
|
64
141
|
const locationTriggersEnabled = enabledTriggers.includes(risk_factor_enum_1.RiskFactor.NEW_COUNTRY) || enabledTriggers.includes(risk_factor_enum_1.RiskFactor.IMPOSSIBLE_TRAVEL);
|
|
65
142
|
if (locationTriggersEnabled &&
|
|
66
143
|
clientInfo.ipCountry &&
|
|
@@ -71,10 +148,17 @@ class RiskDetectionService {
|
|
|
71
148
|
`city=${clientInfo.ipCity ?? 'missing'}, coordinates=${clientInfo.ipLatitude && clientInfo.ipLongitude ? 'available' : 'missing'}. ` +
|
|
72
149
|
`Adding INCOMPLETE_LOCATION_DATA risk factor (+20 points) for reduced confidence in risk assessment.`);
|
|
73
150
|
}
|
|
151
|
+
// ============================================================================
|
|
152
|
+
// IP Risk Detection (only if location unchanged)
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Check new_ip only if country/city hasn't changed
|
|
155
|
+
// IP is the source of location data, so if country/city changed, IP definitely changed
|
|
156
|
+
// Avoid double-counting the same risk factor
|
|
74
157
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.NEW_IP) &&
|
|
75
158
|
clientInfo.ipAddress &&
|
|
76
159
|
!newCountryDetected &&
|
|
77
160
|
!impossibleTravelDetected) {
|
|
161
|
+
// Normalize IP address (remove port if present, e.g., "192.168.1.1:8080" -> "192.168.1.1")
|
|
78
162
|
const normalizedIp = this.normalizeIpAddress(clientInfo.ipAddress);
|
|
79
163
|
const isNew = await this.isNewIp(user.id, normalizedIp);
|
|
80
164
|
if (isNew) {
|
|
@@ -82,6 +166,10 @@ class RiskDetectionService {
|
|
|
82
166
|
this.logger?.debug?.(`New IP detected for user ${user.sub}: ${normalizedIp} (original: ${clientInfo.ipAddress})`);
|
|
83
167
|
}
|
|
84
168
|
}
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Behavioral Risk Detection
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Check suspicious_activity
|
|
85
173
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.SUSPICIOUS_ACTIVITY)) {
|
|
86
174
|
const isSuspicious = await this.detectSuspiciousActivity(user.id);
|
|
87
175
|
if (isSuspicious) {
|
|
@@ -89,6 +177,11 @@ class RiskDetectionService {
|
|
|
89
177
|
this.logger?.debug?.(`Suspicious activity detected for user ${user.sub}`);
|
|
90
178
|
}
|
|
91
179
|
}
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Account Recovery Risk Detection
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Detect if the password was reset/changed after the last successful login.
|
|
184
|
+
// WHY: Many providers treat post-reset sign-in as higher risk and require step-up auth.
|
|
92
185
|
if (enabledTriggers.includes(risk_factor_enum_1.RiskFactor.RECENT_PASSWORD_RESET) &&
|
|
93
186
|
user.passwordChangedAt &&
|
|
94
187
|
user.lastLoginAt &&
|
|
@@ -96,27 +189,47 @@ class RiskDetectionService {
|
|
|
96
189
|
factors.push(risk_factor_enum_1.RiskFactor.RECENT_PASSWORD_RESET);
|
|
97
190
|
this.logger?.debug?.(`Recent password reset detected for user ${user.sub}: passwordChangedAt=${user.passwordChangedAt.toISOString()}, lastLoginAt=${user.lastLoginAt.toISOString()}`);
|
|
98
191
|
}
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Summary
|
|
194
|
+
// ============================================================================
|
|
99
195
|
if (factors.length > 0) {
|
|
100
196
|
this.logger?.debug?.(`Risk detection complete for user ${user.sub}: ${factors.length} factor(s) detected [${factors.join(', ')}]`);
|
|
101
197
|
}
|
|
102
198
|
}
|
|
103
199
|
catch (error) {
|
|
200
|
+
// Non-blocking: Log error but don't throw
|
|
104
201
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
105
202
|
this.logger?.error?.(`Risk detection failed for user ${user.sub}: ${errorMessage}`, { error, userId: user.id });
|
|
203
|
+
// Return empty array on error (graceful degradation)
|
|
106
204
|
return [];
|
|
107
205
|
}
|
|
108
206
|
return factors;
|
|
109
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if device has been seen before
|
|
210
|
+
*
|
|
211
|
+
* Checks trusted devices first (if available), then sessions table.
|
|
212
|
+
* If device is trusted, it's not considered "new" even if no sessions exist yet.
|
|
213
|
+
*
|
|
214
|
+
* @param userId - Internal user ID (integer)
|
|
215
|
+
* @param deviceToken - Device token from client
|
|
216
|
+
* @returns True if device is new (never seen before and not trusted)
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
110
219
|
async isNewDevice(userId, deviceToken) {
|
|
111
220
|
try {
|
|
221
|
+
// First, check if device is trusted (if trusted device service is available)
|
|
222
|
+
// Trusted devices should not be flagged as "new" even if no sessions exist
|
|
112
223
|
if (this.trustedDeviceService && typeof this.trustedDeviceService.isDeviceTrusted === 'function') {
|
|
113
224
|
try {
|
|
114
225
|
const isTrusted = await this.trustedDeviceService.isDeviceTrusted(deviceToken, userId);
|
|
115
226
|
if (isTrusted) {
|
|
227
|
+
// Device is trusted - not a new device
|
|
116
228
|
return false;
|
|
117
229
|
}
|
|
118
230
|
}
|
|
119
231
|
catch (trustedError) {
|
|
232
|
+
// Non-blocking: If trusted device check fails, continue to session check
|
|
120
233
|
const errorMessage = trustedError instanceof Error ? trustedError.message : 'Unknown error';
|
|
121
234
|
this.logger?.warn?.(`Failed to check trusted device: ${errorMessage}`, {
|
|
122
235
|
error: trustedError,
|
|
@@ -125,6 +238,8 @@ class RiskDetectionService {
|
|
|
125
238
|
});
|
|
126
239
|
}
|
|
127
240
|
}
|
|
241
|
+
// Check if any session exists with this deviceId (short-circuit existence)
|
|
242
|
+
// Note: deviceToken is stored as deviceId in sessions
|
|
128
243
|
const exists = await this.sessionRepository.findOne({
|
|
129
244
|
select: ['id'],
|
|
130
245
|
where: { userId, deviceId: deviceToken },
|
|
@@ -132,13 +247,26 @@ class RiskDetectionService {
|
|
|
132
247
|
return !exists;
|
|
133
248
|
}
|
|
134
249
|
catch (error) {
|
|
250
|
+
// Non-blocking: Log and assume device is new (safer default)
|
|
135
251
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
136
252
|
this.logger?.warn?.(`Failed to check device history: ${errorMessage}`, { error, userId, deviceToken });
|
|
137
|
-
return true;
|
|
253
|
+
return true; // Assume new device on error (safer for security)
|
|
138
254
|
}
|
|
139
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Check if user has logged in before (has any previous sessions)
|
|
258
|
+
*
|
|
259
|
+
* Used to determine if missing deviceToken should be treated as suspicious.
|
|
260
|
+
* If user has never logged in before, missing token is expected (first login).
|
|
261
|
+
* If user has logged in before, missing token is suspicious (incognito, cleared cookies, etc.).
|
|
262
|
+
*
|
|
263
|
+
* @param userId - Internal user ID (integer)
|
|
264
|
+
* @returns True if user has at least one previous session
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
140
267
|
async hasUserLoggedInBefore(userId) {
|
|
141
268
|
try {
|
|
269
|
+
// Check if any session exists for this user (short-circuit existence check)
|
|
142
270
|
const exists = await this.sessionRepository.findOne({
|
|
143
271
|
select: ['id'],
|
|
144
272
|
where: { userId },
|
|
@@ -146,31 +274,64 @@ class RiskDetectionService {
|
|
|
146
274
|
return !!exists;
|
|
147
275
|
}
|
|
148
276
|
catch (error) {
|
|
277
|
+
// Non-blocking: Log and assume user has logged in before (safer default)
|
|
149
278
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
150
279
|
this.logger?.warn?.(`Failed to check user login history: ${errorMessage}`, { error, userId });
|
|
151
|
-
return true;
|
|
280
|
+
return true; // Assume user has logged in before on error (safer for security)
|
|
152
281
|
}
|
|
153
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Normalize IP address for consistent comparison
|
|
285
|
+
*
|
|
286
|
+
* Removes port numbers and normalizes IPv6 addresses.
|
|
287
|
+
* This ensures IPs like "192.168.1.1:8080" and "192.168.1.1" are treated as the same.
|
|
288
|
+
*
|
|
289
|
+
* @param ipAddress - IP address to normalize
|
|
290
|
+
* @returns Normalized IP address
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
154
293
|
normalizeIpAddress(ipAddress) {
|
|
294
|
+
// Remove port if present (e.g., "192.168.1.1:8080" -> "192.168.1.1")
|
|
295
|
+
// Handle both IPv4 and IPv6 formats
|
|
155
296
|
if (ipAddress.includes(':')) {
|
|
297
|
+
// Could be IPv6 or IPv4 with port
|
|
156
298
|
if (ipAddress.startsWith('[')) {
|
|
299
|
+
// IPv6 with port: "[::1]:8080" -> "::1"
|
|
157
300
|
const match = ipAddress.match(/^\[(.+)\]:\d+$/);
|
|
158
301
|
if (match) {
|
|
159
302
|
return match[1];
|
|
160
303
|
}
|
|
161
304
|
}
|
|
162
305
|
else {
|
|
306
|
+
// IPv4 with port: "192.168.1.1:8080" -> "192.168.1.1"
|
|
163
307
|
const parts = ipAddress.split(':');
|
|
164
308
|
if (parts.length === 2 && /^\d+$/.test(parts[1])) {
|
|
309
|
+
// Second part is a port number
|
|
165
310
|
return parts[0];
|
|
166
311
|
}
|
|
312
|
+
// Otherwise it's likely IPv6 without brackets, return as-is
|
|
167
313
|
}
|
|
168
314
|
}
|
|
169
315
|
return ipAddress;
|
|
170
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Check if IP address has been seen before
|
|
319
|
+
*
|
|
320
|
+
* Queries sessions table first (faster), then audit table for older data.
|
|
321
|
+
* Uses normalized IP addresses for consistent comparison.
|
|
322
|
+
*
|
|
323
|
+
* @param userId - Internal user ID (integer)
|
|
324
|
+
* @param ipAddress - IP address to check (should already be normalized)
|
|
325
|
+
* @returns True if IP is new (never seen before)
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
171
328
|
async isNewIp(userId, ipAddress) {
|
|
172
329
|
try {
|
|
330
|
+
// Normalize IP address before checking (in case it wasn't normalized upstream)
|
|
173
331
|
const normalizedIp = this.normalizeIpAddress(ipAddress);
|
|
332
|
+
// Check sessions first (faster, more recent data) - existence only
|
|
333
|
+
// Note: We check both normalized and original IP to handle cases where
|
|
334
|
+
// old records might have stored IPs with ports
|
|
174
335
|
const seenInSessions = (await this.sessionRepository.findOne({
|
|
175
336
|
select: ['id'],
|
|
176
337
|
where: { userId, ipAddress: normalizedIp },
|
|
@@ -181,8 +342,9 @@ class RiskDetectionService {
|
|
|
181
342
|
where: { userId, ipAddress },
|
|
182
343
|
})));
|
|
183
344
|
if (seenInSessions) {
|
|
184
|
-
return false;
|
|
345
|
+
return false; // IP seen in sessions
|
|
185
346
|
}
|
|
347
|
+
// Check audit trail for older data (only if not found in sessions)
|
|
186
348
|
const seenInAudit = (await this.auditRepository.findOne({
|
|
187
349
|
select: ['id'],
|
|
188
350
|
where: { userId, ipAddress: normalizedIp },
|
|
@@ -195,47 +357,119 @@ class RiskDetectionService {
|
|
|
195
357
|
return !seenInAudit;
|
|
196
358
|
}
|
|
197
359
|
catch (error) {
|
|
360
|
+
// Non-blocking: Log and assume IP is new (safer default)
|
|
198
361
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
199
362
|
this.logger?.warn?.(`Failed to check IP history: ${errorMessage}`, { error, userId, ipAddress });
|
|
200
|
-
return true;
|
|
363
|
+
return true; // Assume new IP on error (safer for security)
|
|
201
364
|
}
|
|
202
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Check if country has been seen before
|
|
368
|
+
*
|
|
369
|
+
* Queries sessions table for any past session from this country.
|
|
370
|
+
* **Optimization:** Uses 1-2 queries instead of 3 by checking country existence first
|
|
371
|
+
* (most likely to short-circuit), then verifying country data availability if needed.
|
|
372
|
+
*
|
|
373
|
+
* **Important:**
|
|
374
|
+
* - On first login (no previous sessions), returns false (no history to compare)
|
|
375
|
+
* - If sessions exist but none have ipCountry data (null), returns false (can't determine)
|
|
376
|
+
* - Only flags as new if we have sessions with country data AND none match
|
|
377
|
+
*
|
|
378
|
+
* @param userId - Internal user ID (integer)
|
|
379
|
+
* @param country - Country code to check (e.g., 'US', 'GB')
|
|
380
|
+
* @returns True if country is new (never seen before), false on first login, if no country data, or if country seen before
|
|
381
|
+
* @private
|
|
382
|
+
*/
|
|
203
383
|
async isNewCountry(userId, country) {
|
|
204
384
|
try {
|
|
385
|
+
// ============================================================================
|
|
386
|
+
// Optimization: Check country existence first (most likely to short-circuit)
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// This query checks if country exists in sessions with country data
|
|
389
|
+
// If it exists, we can return false immediately (1 query instead of 3)
|
|
205
390
|
const countryExists = await this.sessionRepository.findOne({
|
|
206
391
|
select: ['id'],
|
|
207
392
|
where: { userId, ipCountry: country },
|
|
208
393
|
});
|
|
394
|
+
// If country exists in any session, it's not new
|
|
209
395
|
if (countryExists) {
|
|
210
396
|
return false;
|
|
211
397
|
}
|
|
398
|
+
// ============================================================================
|
|
399
|
+
// Country doesn't exist - verify we have country data to compare against
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Only check if we have sessions with country data (needed to make determination)
|
|
402
|
+
// If no country data exists, we can't determine if it's new (first login or no geo data)
|
|
212
403
|
const hasAnyCountryData = await this.sessionRepository.findOne({
|
|
213
404
|
select: ['id'],
|
|
214
405
|
where: { userId, ipCountry: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()) },
|
|
215
406
|
});
|
|
407
|
+
// If we have sessions with country data but country doesn't exist, it's new
|
|
408
|
+
// If no sessions have country data, we can't determine (return false for safety)
|
|
216
409
|
return !!hasAnyCountryData;
|
|
217
410
|
}
|
|
218
411
|
catch (error) {
|
|
412
|
+
// Non-blocking: Log and assume country is not new on error (safer default)
|
|
219
413
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
220
414
|
this.logger?.warn?.(`Failed to check country history: ${errorMessage}`, { error, userId, country });
|
|
221
|
-
return false;
|
|
415
|
+
return false; // Assume not new country on error (safer default)
|
|
222
416
|
}
|
|
223
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Detect impossible travel
|
|
420
|
+
*
|
|
421
|
+
* Calculates if geographic distance between last location and current
|
|
422
|
+
* location is impossible given time elapsed.
|
|
423
|
+
*
|
|
424
|
+
* **Algorithm:**
|
|
425
|
+
* 1. Get last login location (ipCountry, ipCity, coordinates) and createdAt
|
|
426
|
+
* 2. Calculate distance using coordinates (Haversine) or heuristics (fallback)
|
|
427
|
+
* 3. Calculate max possible speed (distance / time)
|
|
428
|
+
* 4. If speed > threshold (default 900 km/h), flag as impossible
|
|
429
|
+
*
|
|
430
|
+
* **Edge Cases Handled:**
|
|
431
|
+
* - No previous location data → false (benefit of doubt)
|
|
432
|
+
* - Same location → false (not travel)
|
|
433
|
+
* - Missing city but country changed → true (suspicious, different country in short time)
|
|
434
|
+
* - Missing coordinates → use heuristic distance estimates
|
|
435
|
+
*
|
|
436
|
+
* @param userId - Internal user ID (integer)
|
|
437
|
+
* @param currentInfo - Current client info with location
|
|
438
|
+
* @returns True if travel is impossible
|
|
439
|
+
* @private
|
|
440
|
+
*/
|
|
224
441
|
async detectImpossibleTravel(userId, currentInfo) {
|
|
442
|
+
// Require at least country data to perform any checks
|
|
225
443
|
if (!currentInfo.ipCountry) {
|
|
226
444
|
this.logger?.debug?.(`Skipping impossible_travel check for user ${userId}: no country data available`);
|
|
227
445
|
return false;
|
|
228
446
|
}
|
|
229
447
|
try {
|
|
448
|
+
// ============================================================================
|
|
449
|
+
// Find last known location from most recent login
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// CRITICAL: We need the location from the PREVIOUS login, not token refreshes
|
|
452
|
+
// Check BOTH sessions and audit trail to ensure we get the most recent location:
|
|
453
|
+
// - Sessions: might be cleaned up or expired
|
|
454
|
+
// - Audit: permanent record of all logins
|
|
455
|
+
// Use the more recent of the two
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// Find previous login location (exclude current login to avoid race conditions)
|
|
458
|
+
// ============================================================================
|
|
459
|
+
// SIMPLIFIED LOGIC: Just get the most recent login from EITHER sessions OR audits
|
|
460
|
+
// The current login hasn't been committed yet (we're in risk detection before session creation)
|
|
461
|
+
// So the "most recent" we find IS the previous login
|
|
462
|
+
// Check sessions (use createdAt for login-to-login comparison, not lastActivityAt)
|
|
230
463
|
const lastSession = (await this.sessionRepository.findOne({
|
|
231
464
|
where: {
|
|
232
465
|
userId,
|
|
233
466
|
ipCountry: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()),
|
|
234
467
|
},
|
|
235
468
|
order: {
|
|
236
|
-
createdAt: 'DESC',
|
|
469
|
+
createdAt: 'DESC', // Most recent first
|
|
237
470
|
},
|
|
238
471
|
}));
|
|
472
|
+
// Check audit trail for most recent login with location data
|
|
239
473
|
const lastAuditLogin = (await this.auditRepository.findOne({
|
|
240
474
|
where: {
|
|
241
475
|
userId,
|
|
@@ -246,6 +480,12 @@ class RiskDetectionService {
|
|
|
246
480
|
createdAt: 'DESC',
|
|
247
481
|
},
|
|
248
482
|
}));
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// Debug Dumps (optional)
|
|
485
|
+
// ============================================================================
|
|
486
|
+
// WHY: These queries are expensive and should only run when a real NAuthLogger
|
|
487
|
+
// instance is wired (consumer provided a logger). Unit tests frequently use
|
|
488
|
+
// plain object mocks that do NOT implement `isEnabled()`.
|
|
249
489
|
const loggerEnabled = typeof this.logger.isEnabled === 'function'
|
|
250
490
|
? this.logger.isEnabled()
|
|
251
491
|
: false;
|
|
@@ -279,8 +519,10 @@ class RiskDetectionService {
|
|
|
279
519
|
ageMinutes: ((Date.now() - a.createdAt.getTime()) / (1000 * 60)).toFixed(1),
|
|
280
520
|
})))}`);
|
|
281
521
|
}
|
|
522
|
+
// Determine which record is more recent and extract location data with coordinates
|
|
282
523
|
let lastLocation = null;
|
|
283
524
|
if (lastSession && lastAuditLogin) {
|
|
525
|
+
// Both exist - use the more recent one as the previous login
|
|
284
526
|
const sessionTime = lastSession.createdAt;
|
|
285
527
|
const auditTime = lastAuditLogin.createdAt;
|
|
286
528
|
if (sessionTime > auditTime) {
|
|
@@ -326,8 +568,9 @@ class RiskDetectionService {
|
|
|
326
568
|
}
|
|
327
569
|
if (!lastLocation) {
|
|
328
570
|
this.logger?.debug?.(`No previous location data found for user ${userId} (no sessions or audit records)`);
|
|
329
|
-
return false;
|
|
571
|
+
return false; // No previous location data (benefit of doubt)
|
|
330
572
|
}
|
|
573
|
+
// Debug logging to help diagnose issues
|
|
331
574
|
const hasCoordinates = !!(lastLocation.latitude &&
|
|
332
575
|
lastLocation.longitude &&
|
|
333
576
|
currentInfo.ipLatitude &&
|
|
@@ -336,6 +579,7 @@ class RiskDetectionService {
|
|
|
336
579
|
`last=[${lastLocation.city ?? 'unknown'}, ${lastLocation.country} @ ${lastLocation.time.toISOString()}], ` +
|
|
337
580
|
`current=[${currentInfo.ipCity ?? 'unknown'}, ${currentInfo.ipCountry}], ` +
|
|
338
581
|
`coordinates=${hasCoordinates ? 'available' : 'missing'}`);
|
|
582
|
+
// Same location → not travel (only if we have city data to compare)
|
|
339
583
|
if (lastLocation.city &&
|
|
340
584
|
currentInfo.ipCity &&
|
|
341
585
|
lastLocation.country === currentInfo.ipCountry &&
|
|
@@ -343,12 +587,22 @@ class RiskDetectionService {
|
|
|
343
587
|
this.logger?.debug?.(`Same location detected - no travel: user=${userId}, location=[${currentInfo.ipCity}, ${currentInfo.ipCountry}]`);
|
|
344
588
|
return false;
|
|
345
589
|
}
|
|
590
|
+
// Calculate time difference (hours)
|
|
591
|
+
// Risk detection runs BEFORE session creation, so we compare current time to previous login
|
|
592
|
+
// All times are in UTC (database stores timestamps in UTC)
|
|
346
593
|
const now = new Date();
|
|
347
594
|
const hoursSinceLastSeen = (now.getTime() - lastLocation.time.getTime()) / (1000 * 60 * 60);
|
|
348
595
|
this.logger?.debug?.(`Time since last location: ${hoursSinceLastSeen.toFixed(2)} hours (${(hoursSinceLastSeen * 60).toFixed(1)} minutes). ` +
|
|
349
596
|
`Previous login: ${lastLocation.time.toISOString()} (UTC), Current: ${now.toISOString()} (UTC)`);
|
|
597
|
+
// ============================================================================
|
|
598
|
+
// SPECIAL CASE: Country change with missing city data
|
|
599
|
+
// ============================================================================
|
|
600
|
+
// If city data is missing for either location but countries differ,
|
|
601
|
+
// apply conservative threshold for country-level changes
|
|
350
602
|
if (lastLocation.country !== currentInfo.ipCountry) {
|
|
351
603
|
if (!lastLocation.city || !currentInfo.ipCity) {
|
|
604
|
+
// Missing city data - use conservative threshold for country changes
|
|
605
|
+
// If country changed in < threshold hours, flag as suspicious (can't verify exact locations)
|
|
352
606
|
const countryChangeThresholdHours = this.config.mfa?.adaptive?.countryChangeThreshold || 2;
|
|
353
607
|
if (hoursSinceLastSeen < countryChangeThresholdHours) {
|
|
354
608
|
this.logger?.warn?.(`Impossible travel detected (country change without city data): ` +
|
|
@@ -363,8 +617,12 @@ class RiskDetectionService {
|
|
|
363
617
|
return false;
|
|
364
618
|
}
|
|
365
619
|
}
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// NORMAL CASE: Calculate distance using coordinates or heuristics
|
|
622
|
+
// ============================================================================
|
|
366
623
|
let distance;
|
|
367
624
|
let distanceMethod = 'heuristic';
|
|
625
|
+
// Try to use actual coordinates for precise distance calculation
|
|
368
626
|
if (hasCoordinates) {
|
|
369
627
|
distance = this.calculateHaversineDistance(lastLocation.latitude, lastLocation.longitude, currentInfo.ipLatitude, currentInfo.ipLongitude);
|
|
370
628
|
distanceMethod = 'haversine';
|
|
@@ -372,12 +630,14 @@ class RiskDetectionService {
|
|
|
372
630
|
`(${lastLocation.latitude},${lastLocation.longitude} → ${currentInfo.ipLatitude},${currentInfo.ipLongitude})`);
|
|
373
631
|
}
|
|
374
632
|
else {
|
|
633
|
+
// Fallback to heuristic distance estimation
|
|
375
634
|
distance = await this.calculateDistance(lastLocation.city, lastLocation.country, currentInfo.ipCity ?? null, currentInfo.ipCountry);
|
|
376
635
|
this.logger?.debug?.(`Using heuristic distance estimation (coordinates unavailable): distance=${distance}km`);
|
|
377
636
|
}
|
|
378
637
|
if (distance === 0) {
|
|
379
|
-
return false;
|
|
638
|
+
return false; // Same location (shouldn't happen, but safety check)
|
|
380
639
|
}
|
|
640
|
+
// Max realistic speed: 900 km/h (commercial airliner speed)
|
|
381
641
|
const maxTravelSpeed = this.config.mfa?.adaptive?.maxTravelSpeed || 900;
|
|
382
642
|
const requiredSpeed = distance / hoursSinceLastSeen;
|
|
383
643
|
this.logger?.debug?.(`Travel speed calculation (${distanceMethod}): distance=${distance.toFixed(0)}km, ` +
|
|
@@ -397,13 +657,32 @@ class RiskDetectionService {
|
|
|
397
657
|
return isImpossible;
|
|
398
658
|
}
|
|
399
659
|
catch (error) {
|
|
660
|
+
// Non-blocking: Log and assume not impossible travel (safer default)
|
|
400
661
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
401
662
|
this.logger?.warn?.(`Failed to detect impossible travel: ${errorMessage}`, { error, userId });
|
|
402
|
-
return false;
|
|
663
|
+
return false; // Assume not impossible travel on error
|
|
403
664
|
}
|
|
404
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Calculate distance between two points using Haversine formula
|
|
668
|
+
*
|
|
669
|
+
* Accurate distance calculation using geographic coordinates (latitude/longitude).
|
|
670
|
+
* This is the preferred method when coordinates are available.
|
|
671
|
+
*
|
|
672
|
+
* **Haversine Formula:**
|
|
673
|
+
* - Accounts for Earth's spherical shape
|
|
674
|
+
* - Returns great-circle distance in kilometers
|
|
675
|
+
* - Accuracy: ~0.5% for most distances
|
|
676
|
+
*
|
|
677
|
+
* @param lat1 - Latitude of first point (degrees)
|
|
678
|
+
* @param lon1 - Longitude of first point (degrees)
|
|
679
|
+
* @param lat2 - Latitude of second point (degrees)
|
|
680
|
+
* @param lon2 - Longitude of second point (degrees)
|
|
681
|
+
* @returns Distance in kilometers (accurate)
|
|
682
|
+
* @private
|
|
683
|
+
*/
|
|
405
684
|
calculateHaversineDistance(lat1, lon1, lat2, lon2) {
|
|
406
|
-
const R = 6371;
|
|
685
|
+
const R = 6371; // Earth's radius in kilometers
|
|
407
686
|
const dLat = this.toRadians(lat2 - lat1);
|
|
408
687
|
const dLon = this.toRadians(lon2 - lon1);
|
|
409
688
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
@@ -412,28 +691,76 @@ class RiskDetectionService {
|
|
|
412
691
|
const distance = R * c;
|
|
413
692
|
return distance;
|
|
414
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Convert degrees to radians
|
|
696
|
+
*
|
|
697
|
+
* @param degrees - Angle in degrees
|
|
698
|
+
* @returns Angle in radians
|
|
699
|
+
* @private
|
|
700
|
+
*/
|
|
415
701
|
toRadians(degrees) {
|
|
416
702
|
return degrees * (Math.PI / 180);
|
|
417
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* Calculate distance between two cities (heuristic fallback)
|
|
706
|
+
*
|
|
707
|
+
* Heuristic-based implementation for estimating travel distance when
|
|
708
|
+
* precise coordinates are not available. Uses continent and regional
|
|
709
|
+
* groupings for realistic estimates.
|
|
710
|
+
*
|
|
711
|
+
* **Fallback approach:**
|
|
712
|
+
* - Same city: 0 km
|
|
713
|
+
* - Same country, different city: 500 km (average domestic travel)
|
|
714
|
+
* - Different country, same continent: 1,500 km (regional travel)
|
|
715
|
+
* - Different continent (intercontinental): 8,000 km (long-haul flight)
|
|
716
|
+
* - Missing city data: use country-level comparison
|
|
717
|
+
*
|
|
718
|
+
* @param city1 - First city name (nullable)
|
|
719
|
+
* @param country1 - First country code (ISO 2-letter)
|
|
720
|
+
* @param city2 - Second city name (nullable)
|
|
721
|
+
* @param country2 - Second country code (ISO 2-letter)
|
|
722
|
+
* @returns Distance in kilometers (estimated)
|
|
723
|
+
* @private
|
|
724
|
+
*/
|
|
418
725
|
async calculateDistance(city1, country1, city2, country2) {
|
|
726
|
+
// Same city and country → 0 km (only if both cities are known)
|
|
419
727
|
if (city1 && city2 && city1 === city2 && country1 === country2) {
|
|
420
728
|
return 0;
|
|
421
729
|
}
|
|
730
|
+
// Same country → estimate 500 km (average domestic travel)
|
|
422
731
|
if (country1 === country2) {
|
|
423
732
|
return 500;
|
|
424
733
|
}
|
|
734
|
+
// Different country - determine if same continent or intercontinental
|
|
425
735
|
const continent1 = this.getContinent(country1);
|
|
426
736
|
const continent2 = this.getContinent(country2);
|
|
427
737
|
if (continent1 === continent2 && continent1 !== 'unknown') {
|
|
738
|
+
// Same continent, different country → estimate 1,500 km (regional travel)
|
|
739
|
+
// Examples: Paris-Berlin (880km), London-Rome (1,400km), Sydney-Auckland (2,160km)
|
|
428
740
|
return 1500;
|
|
429
741
|
}
|
|
742
|
+
// Different continent → estimate 8,000 km (intercontinental long-haul)
|
|
743
|
+
// Examples: NYC-London (5,570km), Tokyo-Sydney (7,800km), Auckland-Karachi (9,700km)
|
|
744
|
+
// Using 8,000km as realistic average for transcontinental flights
|
|
430
745
|
return 8000;
|
|
431
746
|
}
|
|
747
|
+
/**
|
|
748
|
+
* Get continent for a country code
|
|
749
|
+
*
|
|
750
|
+
* Maps ISO 2-letter country codes to continents for distance estimation.
|
|
751
|
+
* This is used to differentiate between regional and intercontinental travel.
|
|
752
|
+
*
|
|
753
|
+
* @param countryCode - ISO 2-letter country code
|
|
754
|
+
* @returns Continent name
|
|
755
|
+
* @private
|
|
756
|
+
*/
|
|
432
757
|
getContinent(countryCode) {
|
|
433
758
|
const continentMap = {
|
|
759
|
+
// North America
|
|
434
760
|
US: 'north_america',
|
|
435
761
|
CA: 'north_america',
|
|
436
762
|
MX: 'north_america',
|
|
763
|
+
// Europe
|
|
437
764
|
GB: 'europe',
|
|
438
765
|
FR: 'europe',
|
|
439
766
|
DE: 'europe',
|
|
@@ -461,6 +788,7 @@ class RiskDetectionService {
|
|
|
461
788
|
LT: 'europe',
|
|
462
789
|
LV: 'europe',
|
|
463
790
|
EE: 'europe',
|
|
791
|
+
// Asia
|
|
464
792
|
CN: 'asia',
|
|
465
793
|
JP: 'asia',
|
|
466
794
|
IN: 'asia',
|
|
@@ -479,14 +807,17 @@ class RiskDetectionService {
|
|
|
479
807
|
IL: 'asia',
|
|
480
808
|
HK: 'asia',
|
|
481
809
|
TW: 'asia',
|
|
810
|
+
// Oceania
|
|
482
811
|
AU: 'oceania',
|
|
483
812
|
NZ: 'oceania',
|
|
813
|
+
// South America
|
|
484
814
|
BR: 'south_america',
|
|
485
815
|
AR: 'south_america',
|
|
486
816
|
CL: 'south_america',
|
|
487
817
|
CO: 'south_america',
|
|
488
818
|
PE: 'south_america',
|
|
489
819
|
VE: 'south_america',
|
|
820
|
+
// Africa
|
|
490
821
|
ZA: 'africa',
|
|
491
822
|
EG: 'africa',
|
|
492
823
|
NG: 'africa',
|
|
@@ -496,10 +827,24 @@ class RiskDetectionService {
|
|
|
496
827
|
};
|
|
497
828
|
return continentMap[countryCode.toUpperCase()] || 'unknown';
|
|
498
829
|
}
|
|
830
|
+
/**
|
|
831
|
+
* Detect suspicious activity patterns
|
|
832
|
+
*
|
|
833
|
+
* Checks for:
|
|
834
|
+
* - Recent failed login attempts (last 1 hour)
|
|
835
|
+
* - Token reuse detected (SUSPICIOUS_ACTIVITY audit events)
|
|
836
|
+
* - Multiple MFA failures
|
|
837
|
+
* - Account lockout attempts
|
|
838
|
+
*
|
|
839
|
+
* @param userId - Internal user ID (integer)
|
|
840
|
+
* @returns True if suspicious activity detected
|
|
841
|
+
* @private
|
|
842
|
+
*/
|
|
499
843
|
async detectSuspiciousActivity(userId) {
|
|
500
844
|
try {
|
|
501
845
|
const windowHours = this.config.mfa?.adaptive?.suspiciousActivityWindow || 1;
|
|
502
846
|
const oneHourAgo = new Date(Date.now() - windowHours * 60 * 60 * 1000);
|
|
847
|
+
// Check for recent suspicious audit events (existence only)
|
|
503
848
|
const hasSuspicious = await this.auditRepository.findOne({
|
|
504
849
|
select: ['id'],
|
|
505
850
|
where: { userId, eventStatus: 'SUSPICIOUS', createdAt: (0, typeorm_1.MoreThan)(oneHourAgo) },
|
|
@@ -507,6 +852,7 @@ class RiskDetectionService {
|
|
|
507
852
|
if (hasSuspicious) {
|
|
508
853
|
return true;
|
|
509
854
|
}
|
|
855
|
+
// Check for failed login attempts (3+ in last hour) using limited IDs
|
|
510
856
|
const failedLogins = await this.auditRepository.find({
|
|
511
857
|
select: ['id'],
|
|
512
858
|
where: { userId, eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_FAILED, createdAt: (0, typeorm_1.MoreThan)(oneHourAgo) },
|
|
@@ -516,9 +862,10 @@ class RiskDetectionService {
|
|
|
516
862
|
return failedLogins.length >= 3;
|
|
517
863
|
}
|
|
518
864
|
catch (error) {
|
|
865
|
+
// Non-blocking: Log and assume not suspicious (safer default)
|
|
519
866
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
520
867
|
this.logger?.warn?.(`Failed to detect suspicious activity: ${errorMessage}`, { error, userId });
|
|
521
|
-
return false;
|
|
868
|
+
return false; // Assume not suspicious on error
|
|
522
869
|
}
|
|
523
870
|
}
|
|
524
871
|
}
|