feat: added last login time

This commit is contained in:
=
2025-08-07 13:37:06 +05:30
parent be20a507ac
commit eebf080e3c
29 changed files with 156 additions and 63 deletions
@@ -3,39 +3,63 @@ import { Knex } from "knex";
import { TableName } from "../schemas"; import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoggedInAuthMethod"); const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginAuthMethod");
const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn( const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn(
TableName.IdentityOrgMembership, 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) => { 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) => { 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<void> { export async function down(knex: Knex): Promise<void> {
const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoggedInAuthMethod"); const lastUserLoggedInAuthMethod = await knex.schema.hasColumn(TableName.OrgMembership, "lastLoginAuthMethod");
const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn( const lastIdentityLoggedInAuthMethod = await knex.schema.hasColumn(
TableName.IdentityOrgMembership, 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) => { 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) => { await knex.schema.alterTable(TableName.IdentityOrgMembership, (t) => {
t.dropColumn("lastLoggedInAuthMethod"); if (lastIdentityLoggedInAuthMethod) {
t.dropColumn("lastLoginAuthMethod");
}
if (lastIdentityLoggedInTime) {
t.dropColumn("lastLoginTime");
}
}); });
} }
} }
@@ -15,7 +15,8 @@ export const IdentityOrgMembershipsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
identityId: z.string().uuid(), identityId: z.string().uuid(),
lastLoggedInAuthMethod: z.string().nullable().optional() lastLoginAuthMethod: z.string().nullable().optional(),
lastLoginTime: z.date().nullable().optional()
}); });
export type TIdentityOrgMemberships = z.infer<typeof IdentityOrgMembershipsSchema>; export type TIdentityOrgMemberships = z.infer<typeof IdentityOrgMembershipsSchema>;
+2 -1
View File
@@ -20,7 +20,8 @@ export const OrgMembershipsSchema = z.object({
projectFavorites: z.string().array().nullable().optional(), projectFavorites: z.string().array().nullable().optional(),
isActive: z.boolean().default(true), isActive: z.boolean().default(true),
lastInvitedAt: z.date().nullable().optional(), 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<typeof OrgMembershipsSchema>; export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
+2 -2
View File
@@ -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 { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { HsmModule } from "@app/ee/services/hsm/hsm-types"; 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 { 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 { 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"; 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 { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; 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 { 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 { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service"; import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal"; import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
@@ -149,7 +149,10 @@ export const authLoginServiceFactory = ({
if (organizationId) { if (organizationId) {
const org = await orgDAL.findById(organizationId); const org = await orgDAL.findById(organizationId);
if (org) { 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) { if (org.userTokenExpiration) {
tokenSessionExpiresIn = getMinExpiresIn(cfg.JWT_AUTH_LIFETIME, org.userTokenExpiration); tokenSessionExpiresIn = getMinExpiresIn(cfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration; refreshTokenExpiresIn = org.userTokenExpiration;
@@ -32,8 +32,8 @@ import {
keyAlgorithmToAlgCfg keyAlgorithmToAlgCfg
} from "../certificate-authority-fns"; } from "../certificate-authority-fns";
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
import { validateAndMapAltNameType } from "../certificate-authority-validators"; import { validateAndMapAltNameType } from "../certificate-authority-validators";
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
type TInternalCertificateAuthorityFnsDeps = { type TInternalCertificateAuthorityFnsDeps = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">; certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
@@ -52,6 +52,7 @@ import {
} from "../certificate-authority-fns"; } from "../certificate-authority-fns";
import { TCertificateAuthorityQueueFactory } from "../certificate-authority-queue"; import { TCertificateAuthorityQueueFactory } from "../certificate-authority-queue";
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
import { validateAndMapAltNameType } from "../certificate-authority-validators";
import { TInternalCertificateAuthorityDALFactory } from "./internal-certificate-authority-dal"; import { TInternalCertificateAuthorityDALFactory } from "./internal-certificate-authority-dal";
import { import {
TCreateCaDTO, TCreateCaDTO,
@@ -68,7 +69,6 @@ import {
TSignIntermediateDTO, TSignIntermediateDTO,
TUpdateCaDTO TUpdateCaDTO
} from "./internal-certificate-authority-types"; } from "./internal-certificate-authority-types";
import { validateAndMapAltNameType } from "../certificate-authority-validators";
type TInternalCertificateAuthorityServiceFactoryDep = { type TInternalCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick< certificateAuthorityDAL: Pick<
@@ -64,6 +64,8 @@ export const identityAliCloudAuthServiceFactory = ({
identityId: identityAliCloudAuth.identityId identityId: identityAliCloudAuth.identityId
}); });
if (!identityMembershipOrg) throw new UnauthorizedError({ message: "Identity not attached to a organization" });
const requestUrl = new URL("https://sts.aliyuncs.com"); const requestUrl = new URL("https://sts.aliyuncs.com");
for (const key of Object.keys(params)) { for (const key of Object.keys(params)) {
@@ -90,7 +92,8 @@ export const identityAliCloudAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH lastLoginAuthMethod: IdentityAuthMethod.ALICLOUD_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -91,6 +91,7 @@ export const identityAwsAuthServiceFactory = ({
} }
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsAuth.identityId }); 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 headers: TAwsGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString(); const body: string = Buffer.from(iamRequestBody, "base64").toString();
@@ -155,7 +156,8 @@ export const identityAwsAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.AWS_AUTH lastLoginAuthMethod: IdentityAuthMethod.AWS_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -83,7 +83,8 @@ export const identityAzureAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.AZURE_AUTH lastLoginAuthMethod: IdentityAuthMethod.AZURE_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -122,7 +122,8 @@ export const identityGcpAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.GCP_AUTH lastLoginAuthMethod: IdentityAuthMethod.GCP_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -212,7 +212,8 @@ export const identityJwtAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.JWT_AUTH lastLoginAuthMethod: IdentityAuthMethod.JWT_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -383,7 +383,8 @@ export const identityKubernetesAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH lastLoginAuthMethod: IdentityAuthMethod.KUBERNETES_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -147,7 +147,8 @@ export const identityLdapAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.LDAP_AUTH lastLoginAuthMethod: IdentityAuthMethod.LDAP_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -57,6 +57,7 @@ export const identityOciAuthServiceFactory = ({
} }
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityOciAuth.identityId }); 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.<region>.oraclecloud.com" format. // Validate OCI host format. Ensures that the host is in "identity.<region>.oraclecloud.com" format.
if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) { 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( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.OCI_AUTH lastLoginAuthMethod: IdentityAuthMethod.OCI_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -181,7 +181,8 @@ export const identityOidcAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.OIDC_AUTH lastLoginAuthMethod: IdentityAuthMethod.OIDC_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -121,7 +121,8 @@ export const identityTlsCertAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH lastLoginAuthMethod: IdentityAuthMethod.TLS_CERT_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -348,7 +348,8 @@ export const identityTokenAuthServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.TOKEN_AUTH lastLoginAuthMethod: IdentityAuthMethod.TOKEN_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -135,7 +135,8 @@ export const identityUaServiceFactory = ({
await identityOrgMembershipDAL.updateById( await identityOrgMembershipDAL.updateById(
identityMembershipOrg.id, identityMembershipOrg.id,
{ {
lastLoggedInAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH lastLoginAuthMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
lastLoginTime: new Date()
}, },
tx tx
); );
@@ -254,7 +254,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("role").withSchema("paginatedIdentity"), db.ref("role").withSchema("paginatedIdentity"),
db.ref("roleId").withSchema("paginatedIdentity"), db.ref("roleId").withSchema("paginatedIdentity"),
db.ref("orgId").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("createdAt").withSchema("paginatedIdentity"),
db.ref("updatedAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"),
db.ref("identityId").withSchema("paginatedIdentity").as("identityId"), db.ref("identityId").withSchema("paginatedIdentity").as("identityId"),
@@ -321,7 +322,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
tlsCertId, tlsCertId,
createdAt, createdAt,
updatedAt, updatedAt,
lastLoggedInAuthMethod lastLoginAuthMethod,
lastLoginTime
}) => ({ }) => ({
role, role,
roleId, roleId,
@@ -330,7 +332,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
orgId, orgId,
createdAt, createdAt,
updatedAt, updatedAt,
lastLoggedInAuthMethod, lastLoginAuthMethod,
lastLoginTime,
customRole: roleId customRole: roleId
? { ? {
id: crId, id: crId,
@@ -500,7 +503,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("orgId").withSchema(TableName.IdentityOrgMembership), db.ref("orgId").withSchema(TableName.IdentityOrgMembership),
db.ref("createdAt").withSchema(TableName.IdentityOrgMembership), db.ref("createdAt").withSchema(TableName.IdentityOrgMembership),
db.ref("updatedAt").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("identityId").withSchema(TableName.IdentityOrgMembership).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"), db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("hasDeleteProtection").withSchema(TableName.Identity), db.ref("hasDeleteProtection").withSchema(TableName.Identity),
@@ -581,7 +585,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
ldapId, ldapId,
createdAt, createdAt,
updatedAt, updatedAt,
lastLoggedInAuthMethod lastLoginTime,
lastLoginAuthMethod
}) => ({ }) => ({
role, role,
roleId, roleId,
@@ -591,7 +596,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
orgId, orgId,
createdAt, createdAt,
updatedAt, updatedAt,
lastLoggedInAuthMethod, lastLoginTime,
lastLoginAuthMethod,
customRole: roleId customRole: roleId
? { ? {
id: crId, id: crId,
@@ -32,7 +32,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
db.ref("roleId").withSchema(TableName.OrgMembership), db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership), db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").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("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users), db.ref("firstName").withSchema(TableName.Users),
@@ -66,7 +67,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
status, status,
isActive, isActive,
inviteEmail, inviteEmail,
lastLoggedInAuthMethod lastLoginAuthMethod,
lastLoginTime
}) => ({ }) => ({
roleId, roleId,
orgId, orgId,
@@ -75,7 +77,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
status, status,
isActive, isActive,
inviteEmail, inviteEmail,
lastLoggedInAuthMethod, lastLoginAuthMethod,
lastLoginTime,
user: { user: {
id: userId, id: userId,
email, email,
+2 -1
View File
@@ -285,7 +285,8 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("roleId").withSchema(TableName.OrgMembership), db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership), db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").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("email").withSchema(TableName.Users),
db.ref("isEmailVerified").withSchema(TableName.Users), db.ref("isEmailVerified").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
@@ -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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
type Props = { type Props = {
lastLoggedInAuthMethod: string; lastLoginAuthMethod: string;
lastLoginTime: string;
}; };
export const LastLoginSection = ({ lastLoggedInAuthMethod }: Props) => ( export const LastLoginSection = ({ lastLoginTime, lastLoginAuthMethod }: Props) => (
<div> <div>
<div className="mb-2 flex items-center gap-2 border-b border-mineshaft-600 pb-1"> <div className="mb-2 flex items-center gap-2 border-b border-mineshaft-600 pb-1">
<div className="font-medium">Last Login Details</div> <div className="font-medium">Last Login</div>
</div> </div>
<div className="flex items-center gap-2 text-sm"> <div className="mb-2 flex items-center gap-2 text-sm">
<div className="rounded bg-mineshaft-700 p-1 px-2"> <div className="rounded bg-mineshaft-700 p-1 px-2">
<FontAwesomeIcon icon={faShield} /> <FontAwesomeIcon icon={faShield} />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<div className="text-sm font-medium">Authentication Method</div> <div className="text-sm font-medium">Authentication Method</div>
<div className="text-sm">{lastLoggedInAuthMethod}</div> <div className="text-sm">{lastLoginAuthMethod}</div>
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<div className="rounded bg-mineshaft-700 p-1 px-2">
<FontAwesomeIcon icon={faClock} />
</div>
<div className="flex flex-col">
<div className="text-sm font-medium">Time</div>
<div className="text-sm">{format(lastLoginTime, "PPpp")} </div>
</div> </div>
</div> </div>
</div> </div>
+2 -1
View File
@@ -41,7 +41,8 @@ export type IdentityMembershipOrg = {
id: string; id: string;
identity: Identity; identity: Identity;
organization: string; organization: string;
lastLoggedInAuthMethod?: IdentityAuthMethod; lastLoginAuthMethod?: IdentityAuthMethod;
lastLoginTime?: string;
metadata: { key: string; value: string; id: string }[]; metadata: { key: string; value: string; id: string }[];
role: "admin" | "member" | "viewer" | "no-access" | "custom"; role: "admin" | "member" | "viewer" | "no-access" | "custom";
customRole?: TOrgRole; customRole?: TOrgRole;
+2 -1
View File
@@ -68,7 +68,8 @@ export type OrgUser = {
deniedPermissions: any[]; deniedPermissions: any[];
roleId: string; roleId: string;
isActive: boolean; isActive: boolean;
lastLoggedInAuthMethod?: AuthMethod; lastLoginAuthMethod?: AuthMethod;
lastLoginTime?: string;
}; };
export type TProjectMembership = { export type TProjectMembership = {
@@ -293,7 +293,13 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
{isPending && <TableSkeleton columns={3} innerKey="org-identities" />} {isPending && <TableSkeleton columns={3} innerKey="org-identities" />}
{!isPending && {!isPending &&
data?.identities?.map( data?.identities?.map(
({ identity: { id, name }, role, customRole, lastLoggedInAuthMethod }) => { ({
identity: { id, name },
role,
customRole,
lastLoginAuthMethod,
lastLoginTime
}) => {
return ( return (
<Tr <Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700" className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
@@ -309,14 +315,13 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
> >
<Td className="group"> <Td className="group">
{name} {name}
{lastLoggedInAuthMethod && ( {lastLoginAuthMethod && lastLoginTime && (
<Tooltip <Tooltip
className="min-w-52 max-w-96" className="min-w-52 max-w-96"
content={ content={
<LastLoginSection <LastLoginSection
lastLoggedInAuthMethod={ lastLoginAuthMethod={identityAuthToNameMap[lastLoginAuthMethod]}
identityAuthToNameMap[lastLoggedInAuthMethod] lastLoginTime={lastLoginTime}
}
/> />
} }
> >
@@ -481,7 +481,8 @@ export const OrgMembersTable = ({
id: orgMembershipId, id: orgMembershipId,
status, status,
isActive, isActive,
lastLoggedInAuthMethod lastLoginAuthMethod,
lastLoginTime
}) => { }) => {
const name = const name =
u && u.firstName ? `${u.firstName} ${u.lastName ?? ""}`.trim() : null; u && u.firstName ? `${u.firstName} ${u.lastName ?? ""}`.trim() : null;
@@ -530,11 +531,14 @@ export const OrgMembersTable = ({
</Tooltip> </Tooltip>
</Badge> </Badge>
)} )}
{lastLoggedInAuthMethod && ( {lastLoginAuthMethod && lastLoginTime && (
<Tooltip <Tooltip
className="min-w-52 max-w-96" className="min-w-52 max-w-96"
content={ content={
<LastLoginSection lastLoggedInAuthMethod={lastLoggedInAuthMethod} /> <LastLoginSection
lastLoginAuthMethod={lastLoginAuthMethod}
lastLoginTime={lastLoginTime}
/>
} }
> >
<FontAwesomeIcon <FontAwesomeIcon
@@ -24,6 +24,7 @@ import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/contex
import { useTimedReset } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api"; import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { format } from "date-fns";
type Props = { type Props = {
identityId: string; identityId: string;
@@ -141,7 +142,13 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =
<div className="mb-4"> <div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Last Login Auth Method</p> <p className="text-sm font-semibold text-mineshaft-300">Last Login Auth Method</p>
<p className="text-sm text-mineshaft-300"> <p className="text-sm text-mineshaft-300">
{data.lastLoggedInAuthMethod ? identityAuthToNameMap[data.lastLoggedInAuthMethod] : "-"} {data.lastLoginAuthMethod ? identityAuthToNameMap[data.lastLoginAuthMethod] : "-"}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Last Login Time</p>
<p className="text-sm text-mineshaft-300">
{data.lastLoginTime ? format(data.lastLoginTime, "PPpp") : "-"}
</p> </p>
</div> </div>
<div className="mb-4"> <div className="mb-4">
@@ -22,6 +22,7 @@ import { useFetchServerStatus, useGetOrgMembership, useGetOrgRoles } from "@app/
import { OrgUser } from "@app/hooks/api/types"; import { OrgUser } from "@app/hooks/api/types";
import { useResendOrgMemberInvitation } from "@app/hooks/api/users/mutation"; import { useResendOrgMemberInvitation } from "@app/hooks/api/users/mutation";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { format } from "date-fns";
type Props = { type Props = {
membershipId: string; membershipId: string;
@@ -163,7 +164,15 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) =>
<p className="text-sm font-semibold text-mineshaft-300">Last Login Auth Method</p> <p className="text-sm font-semibold text-mineshaft-300">Last Login Auth Method</p>
<div className="group flex align-top"> <div className="group flex align-top">
<p className="break-all text-sm text-mineshaft-300"> <p className="break-all text-sm text-mineshaft-300">
{membership.lastLoggedInAuthMethod || "-"} {membership.lastLoginAuthMethod || "-"}
</p>
</div>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Last Login Time</p>
<div className="group flex align-top">
<p className="break-all text-sm text-mineshaft-300">
{membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}
</p> </p>
</div> </div>
</div> </div>