diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index c88874499..f47048878 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -773,7 +773,8 @@ export const registerRoutes = async ( orgDAL, totpService, orgMembershipDAL, - auditLogService + auditLogService, + notificationService }); const passwordService = authPaswordServiceFactory({ tokenService, diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index ede9e29e3..2a680b9b8 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -14,6 +14,8 @@ import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service"; import { TokenType } from "../auth-token/auth-token-types"; +import { TNotificationServiceFactory } from "../notification/notification-service"; +import { NotificationType } from "../notification/notification-types"; import { TOrgDALFactory } from "../org/org-dal"; import { getDefaultOrgMembershipRole } from "../org/org-role-fns"; import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal"; @@ -47,6 +49,7 @@ type TAuthLoginServiceFactoryDep = { totpService: Pick; auditLogService: Pick; orgMembershipDAL: TOrgMembershipDALFactory; + notificationService: Pick; }; export type TAuthLoginFactory = ReturnType; @@ -57,7 +60,8 @@ export const authLoginServiceFactory = ({ orgDAL, orgMembershipDAL, totpService, - auditLogService + auditLogService, + notificationService }: TAuthLoginServiceFactoryDep) => { /* * Private @@ -71,6 +75,16 @@ export const authLoginServiceFactory = ({ if (!isDeviceSeen) { const newDeviceList = devices.concat([{ ip, userAgent }]); await userDAL.updateById(user.id, { devices: JSON.stringify(newDeviceList) }, tx); + + await notificationService.createUserNotifications([ + { + userId: user.id, + type: NotificationType.LOGIN_FROM_NEW_DEVICE, + title: "Login From New Device", + body: `A new device with IP **${ip}** and User Agent **${userAgent}** has logged into your account.` + } + ]); + if (user.email) { await smtpService.sendMail({ template: SmtpTemplates.NewDeviceJoin, @@ -563,6 +577,18 @@ export const authLoginServiceFactory = ({ .filter(Boolean) as string[]; if (adminEmails.length > 0) { + await notificationService.createUserNotifications( + orgAdmins + .filter((admin) => admin.user.id !== user.id) + .map((admin) => ({ + userId: admin.user.id, + orgId: organizationId, + type: NotificationType.ADMIN_SSO_BYPASS, + title: "Security Alert: Admin SSO Bypass", + body: `The org admin **${user.email}** has bypassed enforced SSO login.` + })) + ); + await smtpService.sendMail({ recipients: adminEmails, subjectLine: "Security Alert: Admin SSO Bypass", diff --git a/backend/src/services/notification/notification-types.ts b/backend/src/services/notification/notification-types.ts index e33bafcb9..3377afbc9 100644 --- a/backend/src/services/notification/notification-types.ts +++ b/backend/src/services/notification/notification-types.ts @@ -6,7 +6,9 @@ export enum NotificationType { SECRET_CHANGE_POLICY_BYPASSED = "secret-change-policy-bypassed", SECRET_ROTATION_FAILED = "secret-rotation-failed", SECRET_SCANNING_SECRETS_DETECTED = "secret-scanning-secrets-detected", - SECRET_SCANNING_SCAN_FAILED = "secret-scanning-scan-failed" + SECRET_SCANNING_SCAN_FAILED = "secret-scanning-scan-failed", + LOGIN_FROM_NEW_DEVICE = "login-from-new-device", + ADMIN_SSO_BYPASS = "admin-sso-bypass" } export interface TCreateUserNotificationDTO {