From eebf080e3ca1c68de513d0565125ad2df12a7df7 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 7 Aug 2025 13:37:06 +0530 Subject: [PATCH] feat: added last login time --- .../20250805151349_last-logged-auth-method.ts | 48 ++++++++++++++----- .../db/schemas/identity-org-memberships.ts | 3 +- backend/src/db/schemas/org-memberships.ts | 3 +- backend/src/server/routes/index.ts | 4 +- .../src/services/auth/auth-login-service.ts | 5 +- .../internal-certificate-authority-fns.ts | 2 +- .../internal-certificate-authority-service.ts | 2 +- .../identity-alicloud-auth-service.ts | 5 +- .../identity-aws-auth-service.ts | 4 +- .../identity-azure-auth-service.ts | 3 +- .../identity-gcp-auth-service.ts | 3 +- .../identity-jwt-auth-service.ts | 3 +- .../identity-kubernetes-auth-service.ts | 3 +- .../identity-ldap-auth-service.ts | 3 +- .../identity-oci-auth-service.ts | 4 +- .../identity-oidc-auth-service.ts | 3 +- .../identity-tls-cert-auth-service.ts | 3 +- .../identity-token-auth-service.ts | 3 +- .../identity-ua/identity-ua-service.ts | 3 +- .../src/services/identity/identity-org-dal.ts | 26 ++++++---- .../org-membership/org-membership-dal.ts | 9 ++-- backend/src/services/org/org-dal.ts | 3 +- .../LastLoginSection/LastLoginSection.tsx | 23 ++++++--- frontend/src/hooks/api/identities/types.ts | 3 +- frontend/src/hooks/api/users/types.ts | 3 +- .../IdentitySection/IdentityTable.tsx | 15 ++++-- .../OrgMembersSection/OrgMembersTable.tsx | 10 ++-- .../components/IdentityDetailsSection.tsx | 9 +++- .../components/UserDetailsSection.tsx | 11 ++++- 29 files changed, 156 insertions(+), 63 deletions(-) diff --git a/backend/src/db/migrations/20250805151349_last-logged-auth-method.ts b/backend/src/db/migrations/20250805151349_last-logged-auth-method.ts index 0a05e9b22..a373e0a91 100644 --- a/backend/src/db/migrations/20250805151349_last-logged-auth-method.ts +++ b/backend/src/db/migrations/20250805151349_last-logged-auth-method.ts @@ -3,39 +3,63 @@ import { Knex } from "knex"; import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { - const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoggedInAuthMethod"); + const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginAuthMethod"); const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn( TableName.IdentityOrgMembership, - "lastLoggedInAuthMethod" + "lastLoginAuthMethod" ); - if (!lastUserLoggedInAuthMethod) { + const lastUserLoggedInTime = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginTime"); + const lastIdentityLoggedInTime = await knex.schema.hasColumn(TableName.IdentityOrgMembership, "lastLoginTime"); + if (!lastUserLoggedInAuthMethod || !lastUserLoggedInTime) { await knex.schema.alterTable(TableName.OrgMembership, (t) => { - t.string("lastLoggedInAuthMethod").nullable(); + if (!lastUserLoggedInAuthMethod) { + t.string("lastLoginAuthMethod").nullable(); + } + if (!lastUserLoggedInTime) { + t.datetime("lastLoginTime").nullable(); + } }); } - if (!lastIdentityLoggedInAuthMethod) { + if (!lastIdentityLoggedInAuthMethod || !lastIdentityLoggedInTime) { await knex.schema.alterTable(TableName.IdentityOrgMembership, (t) => { - t.string("lastLoggedInAuthMethod").nullable(); + if (!lastIdentityLoggedInAuthMethod) { + t.string("lastLoginAuthMethod").nullable(); + } + if (!lastIdentityLoggedInTime) { + t.datetime("lastLoginTime").nullable(); + } }); } } export async function down(knex: Knex): Promise { - const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoggedInAuthMethod"); + const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginAuthMethod"); const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn( TableName.IdentityOrgMembership, - "lastLoggedInAuthMethod" + "lastLoginAuthMethod" ); - if (lastUserLoggedInAuthMethod) { + const lastUserLoggedInTime = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginTime"); + const lastIdentityLoggedInTime = await knex.schema.hasColumn(TableName.IdentityOrgMembership, "lastLoginTime"); + if (lastUserLoggedInAuthMethod || lastUserLoggedInTime) { await knex.schema.alterTable(TableName.OrgMembership, (t) => { - t.dropColumn("lastLoggedInAuthMethod"); + if (lastUserLoggedInAuthMethod) { + t.dropColumn("lastLoginAuthMethod"); + } + if (lastUserLoggedInTime) { + t.dropColumn("lastLoginTime"); + } }); } - if (lastIdentityLoggedInAuthMethod) { + if (lastIdentityLoggedInAuthMethod || lastIdentityLoggedInTime) { await knex.schema.alterTable(TableName.IdentityOrgMembership, (t) => { - t.dropColumn("lastLoggedInAuthMethod"); + if (lastIdentityLoggedInAuthMethod) { + t.dropColumn("lastLoginAuthMethod"); + } + if (lastIdentityLoggedInTime) { + t.dropColumn("lastLoginTime"); + } }); } } diff --git a/backend/src/db/schemas/identity-org-memberships.ts b/backend/src/db/schemas/identity-org-memberships.ts index 2f0c81a3a..85cba8dfd 100644 --- a/backend/src/db/schemas/identity-org-memberships.ts +++ b/backend/src/db/schemas/identity-org-memberships.ts @@ -15,7 +15,8 @@ export const IdentityOrgMembershipsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), identityId: z.string().uuid(), - lastLoggedInAuthMethod: z.string().nullable().optional() + lastLoginAuthMethod: z.string().nullable().optional(), + lastLoginTime: z.date().nullable().optional() }); export type TIdentityOrgMemberships = z.infer; diff --git a/backend/src/db/schemas/org-memberships.ts b/backend/src/db/schemas/org-memberships.ts index 5be83be00..2112870d1 100644 --- a/backend/src/db/schemas/org-memberships.ts +++ b/backend/src/db/schemas/org-memberships.ts @@ -20,7 +20,8 @@ export const OrgMembershipsSchema = z.object({ projectFavorites: z.string().array().nullable().optional(), isActive: z.boolean().default(true), lastInvitedAt: z.date().nullable().optional(), - lastLoggedInAuthMethod: z.string().nullable().optional() + lastLoginAuthMethod: z.string().nullable().optional(), + lastLoginTime: z.date().nullable().optional() }); export type TOrgMemberships = z.infer; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 64cfb140e..c4f0de34a 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -45,6 +45,8 @@ import { groupServiceFactory } from "@app/ee/services/group/group-service"; import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { HsmModule } from "@app/ee/services/hsm/hsm-types"; +import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal"; +import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service"; import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal"; import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service"; import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service"; @@ -179,8 +181,6 @@ import { identityAccessTokenDALFactory } from "@app/services/identity-access-tok import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service"; -import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal"; -import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service"; import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal"; import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service"; import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal"; diff --git a/backend/src/services/auth/auth-login-service.ts b/backend/src/services/auth/auth-login-service.ts index 4d6830834..ab0f7deba 100644 --- a/backend/src/services/auth/auth-login-service.ts +++ b/backend/src/services/auth/auth-login-service.ts @@ -149,7 +149,10 @@ export const authLoginServiceFactory = ({ if (organizationId) { const org = await orgDAL.findById(organizationId); if (org) { - await orgMembershipDAL.update({ userId: user.id, orgId: org.id }, { lastLoggedInAuthMethod: authMethod }); + await orgMembershipDAL.update( + { userId: user.id, orgId: org.id }, + { lastLoginAuthMethod: authMethod, lastLoginTime: new Date() } + ); if (org.userTokenExpiration) { tokenSessionExpiresIn = getMinExpiresIn(cfg.JWT_AUTH_LIFETIME, org.userTokenExpiration); refreshTokenExpiresIn = org.userTokenExpiration; diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts index 6811476f8..5671435f6 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts @@ -32,8 +32,8 @@ import { keyAlgorithmToAlgCfg } from "../certificate-authority-fns"; import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal"; -import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types"; import { validateAndMapAltNameType } from "../certificate-authority-validators"; +import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types"; type TInternalCertificateAuthorityFnsDeps = { certificateAuthorityDAL: Pick; diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts index 510a160e9..9fed186bc 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts @@ -52,6 +52,7 @@ import { } from "../certificate-authority-fns"; import { TCertificateAuthorityQueueFactory } from "../certificate-authority-queue"; import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal"; +import { validateAndMapAltNameType } from "../certificate-authority-validators"; import { TInternalCertificateAuthorityDALFactory } from "./internal-certificate-authority-dal"; import { TCreateCaDTO, @@ -68,7 +69,6 @@ import { TSignIntermediateDTO, TUpdateCaDTO } from "./internal-certificate-authority-types"; -import { validateAndMapAltNameType } from "../certificate-authority-validators"; type TInternalCertificateAuthorityServiceFactoryDep = { certificateAuthorityDAL: Pick< diff --git a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts index fe77aa2c2..af94c79c9 100644 --- a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts +++ b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts @@ -64,6 +64,8 @@ export const identityAliCloudAuthServiceFactory = ({ identityId: identityAliCloudAuth.identityId }); + if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" }); + const requestUrl = new URL("https://sts.aliyuncs.com"); for (const key of Object.keys(params)) { @@ -90,7 +92,8 @@ export const identityAliCloudAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH + lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 3743934ed..3dff47403 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -91,6 +91,7 @@ export const identityAwsAuthServiceFactory = ({ } const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId }); + if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" }); const headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString()); const body: string = Buffer.from(iamRequestBody, "base64").toString(); @@ -155,7 +156,8 @@ export const identityAwsAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.AWS_AUTH + lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts index bbd9194b1..9a2426a7c 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts @@ -83,7 +83,8 @@ export const identityAzureAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.AZURE_AUTH + lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts index 09a3511ef..388c24d48 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts @@ -122,7 +122,8 @@ export const identityGcpAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.GCP_AUTH + lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts index 3f0b1f5a5..7b0a19414 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts @@ -212,7 +212,8 @@ export const identityJwtAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.JWT_AUTH + lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 2329285f0..9584b122a 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -383,7 +383,8 @@ export const identityKubernetesAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH + lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts index a94e93d11..47188e26d 100644 --- a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts +++ b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts @@ -147,7 +147,8 @@ export const identityLdapAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.LDAP_AUTH + lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts index baaa5b423..a4294250c 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts @@ -57,6 +57,7 @@ export const identityOciAuthServiceFactory = ({ } const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityOciAuth.identityId }); + if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" }); // Validate OCI host format. Ensures that the host is in "identity..oraclecloud.com" format. if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) { @@ -94,7 +95,8 @@ export const identityOciAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.OCI_AUTH + lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index c7e064428..617b21a1f 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -181,7 +181,8 @@ export const identityOidcAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.OIDC_AUTH + lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts index b99de47a3..ef2463eec 100644 --- a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts @@ -121,7 +121,8 @@ export const identityTlsCertAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH + lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 7903732ac..d3743bd96 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -348,7 +348,8 @@ export const identityTokenAuthServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.TOKEN_AUTH + lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index 5732992e5..cd7211b5c 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -135,7 +135,8 @@ export const identityUaServiceFactory = ({ await identityOrgMembershipDAL.updateById( identityMembershipOrg.id, { - lastLoggedInAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH + lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH, + lastLoginTime: new Date() }, tx ); diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 6dbf4be9b..c083df5aa 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -254,7 +254,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("role").withSchema("paginatedIdentity"), db.ref("roleId").withSchema("paginatedIdentity"), db.ref("orgId").withSchema("paginatedIdentity"), - db.ref("lastLoggedInAuthMethod").withSchema("paginatedIdentity"), + db.ref("lastLoginAuthMethod").withSchema("paginatedIdentity"), + db.ref("lastLoginTime").withSchema("paginatedIdentity"), db.ref("createdAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"), db.ref("identityId").withSchema("paginatedIdentity").as("identityId"), @@ -321,7 +322,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { tlsCertId, createdAt, updatedAt, - lastLoggedInAuthMethod + lastLoginAuthMethod, + lastLoginTime }) => ({ role, roleId, @@ -330,7 +332,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { orgId, createdAt, updatedAt, - lastLoggedInAuthMethod, + lastLoginAuthMethod, + lastLoginTime, customRole: roleId ? { id: crId, @@ -500,7 +503,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("orgId").withSchema(TableName.IdentityOrgMembership), db.ref("createdAt").withSchema(TableName.IdentityOrgMembership), db.ref("updatedAt").withSchema(TableName.IdentityOrgMembership), - db.ref("lastLoggedInAuthMethod").withSchema(TableName.IdentityOrgMembership), + db.ref("lastLoginAuthMethod").withSchema(TableName.IdentityOrgMembership), + db.ref("lastLoginTime").withSchema(TableName.IdentityOrgMembership), db.ref("identityId").withSchema(TableName.IdentityOrgMembership).as("identityId"), db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("hasDeleteProtection").withSchema(TableName.Identity), @@ -535,10 +539,10 @@ export const identityOrgDALFactory = (db: TDbClient) => { } else if (orderBy === OrgIdentityOrderBy.Role) { void query.orderByRaw( ` - CASE - WHEN ??.role = ? - THEN ??.slug - ELSE ??.role + CASE + WHEN ??.role = ? + THEN ??.slug + ELSE ??.role END ? `, [ @@ -581,7 +585,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { ldapId, createdAt, updatedAt, - lastLoggedInAuthMethod + lastLoginTime, + lastLoginAuthMethod }) => ({ role, roleId, @@ -591,7 +596,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { orgId, createdAt, updatedAt, - lastLoggedInAuthMethod, + lastLoginTime, + lastLoginAuthMethod, customRole: roleId ? { id: crId, diff --git a/backend/src/services/org-membership/org-membership-dal.ts b/backend/src/services/org-membership/org-membership-dal.ts index 98e89c832..8f2ca01f0 100644 --- a/backend/src/services/org-membership/org-membership-dal.ts +++ b/backend/src/services/org-membership/org-membership-dal.ts @@ -32,7 +32,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => { db.ref("roleId").withSchema(TableName.OrgMembership), db.ref("status").withSchema(TableName.OrgMembership), db.ref("isActive").withSchema(TableName.OrgMembership), - db.ref("lastLoggedInAuthMethod").withSchema(TableName.OrgMembership), + db.ref("lastLoginAuthMethod").withSchema(TableName.OrgMembership), + db.ref("lastLoginTime").withSchema(TableName.OrgMembership), db.ref("email").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users), db.ref("firstName").withSchema(TableName.Users), @@ -66,7 +67,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => { status, isActive, inviteEmail, - lastLoggedInAuthMethod + lastLoginAuthMethod, + lastLoginTime }) => ({ roleId, orgId, @@ -75,7 +77,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => { status, isActive, inviteEmail, - lastLoggedInAuthMethod, + lastLoginAuthMethod, + lastLoginTime, user: { id: userId, email, diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts index 9f085284a..b46efc46c 100644 --- a/backend/src/services/org/org-dal.ts +++ b/backend/src/services/org/org-dal.ts @@ -285,7 +285,8 @@ export const orgDALFactory = (db: TDbClient) => { db.ref("roleId").withSchema(TableName.OrgMembership), db.ref("status").withSchema(TableName.OrgMembership), db.ref("isActive").withSchema(TableName.OrgMembership), - db.ref("lastLoggedInAuthMethod").withSchema(TableName.OrgMembership), + db.ref("lastLoginAuthMethod").withSchema(TableName.OrgMembership), + db.ref("lastLoginTime").withSchema(TableName.OrgMembership), db.ref("email").withSchema(TableName.Users), db.ref("isEmailVerified").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users), diff --git a/frontend/src/components/organization/LastLoginSection/LastLoginSection.tsx b/frontend/src/components/organization/LastLoginSection/LastLoginSection.tsx index 7a4e7b032..5954f667b 100644 --- a/frontend/src/components/organization/LastLoginSection/LastLoginSection.tsx +++ b/frontend/src/components/organization/LastLoginSection/LastLoginSection.tsx @@ -1,22 +1,33 @@ -import { faShield } from "@fortawesome/free-solid-svg-icons"; +import { faShield, faClock } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { format } from "date-fns"; type Props = { - lastLoggedInAuthMethod: string; + lastLoginAuthMethod: string; + lastLoginTime: string; }; -export const LastLoginSection = ({ lastLoggedInAuthMethod }: Props) => ( +export const LastLoginSection = ({ lastLoginTime, lastLoginAuthMethod }: Props) => (
-
Last Login Details
+
Last Login
-
+
Authentication Method
-
{lastLoggedInAuthMethod}
+
{lastLoginAuthMethod}
+
+
+
+
+ +
+
+
Time
+
{format(lastLoginTime, "PPpp")}
diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index 13472347b..7098ce244 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -41,7 +41,8 @@ export type IdentityMembershipOrg = { id: string; identity: Identity; organization: string; - lastLoggedInAuthMethod?: IdentityAuthMethod; + lastLoginAuthMethod?: IdentityAuthMethod; + lastLoginTime?: string; metadata: { key: string; value: string; id: string }[]; role: "admin" | "member" | "viewer" | "no-access" | "custom"; customRole?: TOrgRole; diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index ff25846cb..3af283b7e 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -68,7 +68,8 @@ export type OrgUser = { deniedPermissions: any[]; roleId: string; isActive: boolean; - lastLoggedInAuthMethod?: AuthMethod; + lastLoginAuthMethod?: AuthMethod; + lastLoginTime?: string; }; export type TProjectMembership = { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index 98fac6b84..08ac6046d 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -293,7 +293,13 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { {isPending && } {!isPending && data?.identities?.map( - ({ identity: { id, name }, role, customRole, lastLoggedInAuthMethod }) => { + ({ + identity: { id, name }, + role, + customRole, + lastLoginAuthMethod, + lastLoginTime + }) => { return ( { > {name} - {lastLoggedInAuthMethod && ( + {lastLoginAuthMethod && lastLoginTime && ( } > diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index 4788c51cd..3845676d7 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -481,7 +481,8 @@ export const OrgMembersTable = ({ id: orgMembershipId, status, isActive, - lastLoggedInAuthMethod + lastLoginAuthMethod, + lastLoginTime }) => { const name = u && u.firstName ? `${u.firstName} ${u.lastName ?? ""}`.trim() : null; @@ -530,11 +531,14 @@ export const OrgMembersTable = ({ )} - {lastLoggedInAuthMethod && ( + {lastLoginAuthMethod && lastLoginTime && ( + } >

Last Login Auth Method

- {data.lastLoggedInAuthMethod ? identityAuthToNameMap[data.lastLoggedInAuthMethod] : "-"} + {data.lastLoginAuthMethod ? identityAuthToNameMap[data.lastLoginAuthMethod] : "-"} +

+
+
+

Last Login Time

+

+ {data.lastLoginTime ? format(data.lastLoginTime, "PPpp") : "-"}

diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx index 50f3804d0..24dc581b4 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx @@ -22,6 +22,7 @@ import { useFetchServerStatus, useGetOrgMembership, useGetOrgRoles } from "@app/ import { OrgUser } from "@app/hooks/api/types"; import { useResendOrgMemberInvitation } from "@app/hooks/api/users/mutation"; import { UsePopUpState } from "@app/hooks/usePopUp"; +import { format } from "date-fns"; type Props = { membershipId: string; @@ -163,7 +164,15 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>

Last Login Auth Method

- {membership.lastLoggedInAuthMethod || "-"} + {membership.lastLoginAuthMethod || "-"} +

+
+
+
+

Last Login Time

+
+

+ {membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}