Skip to content

Commit

Permalink
fix: kratos user list pagination (#820)
Browse files Browse the repository at this point in the history
  • Loading branch information
m8vago authored Sep 14, 2023
1 parent 72a3185 commit ab2711d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 21 deletions.
17 changes: 16 additions & 1 deletion web/crux/src/app/audit/audit.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'
import { Identity } from '@ory/kratos-client'
import { AuditLog, AuditLogActorTypeEnum, DeploymentToken } from '@prisma/client'
import { emailOfIdentity, nameOfIdentity } from 'src/domain/identity'
import { AuditDto, AuditLogActorTypeDto, AuditLogDto } from './audit.dto'
import { AuditDto, AuditLogActorTypeDto, AuditLogDto, AuditLogUserDto } from './audit.dto'

@Injectable()
export default class AuditMapper {
Expand Down Expand Up @@ -36,6 +36,14 @@ export default class AuditMapper {

if (it.actorType === 'user') {
const identity = identities.get(it.userId)
if (!identity) {
return {
...base,
name: AuditMapper.UNKNOWN_USER_NAME,
user: AuditMapper.UNKNOWN_USER,
}
}

return {
...base,
name: nameOfIdentity(identity),
Expand All @@ -51,6 +59,13 @@ export default class AuditMapper {
name: it.deploymentToken.name,
}
}

private static readonly UNKNOWN_USER_NAME = 'Unknown User'

private static readonly UNKNOWN_USER: AuditLogUserDto = {
email: '',
id: '',
}
}

type Audit = {
Expand Down
6 changes: 2 additions & 4 deletions web/crux/src/app/audit/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,12 @@ export default class AuditService {
return {}
}

const userIds = await this.kratos.getIdentityIdsByEmail(filter)
const user = await this.kratos.getIdentityByEmail(filter)

return {
OR: [
{
userId: {
in: userIds,
},
userId: user.id,
},
{
event: {
Expand Down
72 changes: 56 additions & 16 deletions web/crux/src/services/kratos.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { Configuration, FrontendApi, Identity, IdentityApi, Session } from '@ory/kratos-client'
import { AxiosResponse } from 'axios'
import { randomUUID } from 'crypto'
import { setDefaultResultOrder } from 'dns'
import http from 'http'
import { IdentityTraits, KRATOS_IDENTITY_SCHEMA, KratosInvitation } from 'src/domain/identity'
import { PRODUCTION } from 'src/shared/const'
import { KRATOS_LIST_PAGE_SIZE, PRODUCTION } from 'src/shared/const'

type KratosListHeaders = {
link?: string
}

const KRATOS_LIST_REL_NEXT = '; rel="next"'

@Injectable()
export default class KratosService {
Expand All @@ -26,17 +33,60 @@ export default class KratosService {
}

async getIdentityByEmail(email: string): Promise<Identity> {
const identities = await this.identity.listIdentities()
const identities = await this.identity.listIdentities({
credentialsIdentifier: email,
})

return identities.data.find(user => {
const traits = user.traits as IdentityTraits
return traits.email === email
})
}

async getIdentitiesByIds(ids: Set<string>): Promise<Map<string, Identity>> {
const identities = await this.identity.listIdentities()
async getIdentitiesByIds(identityIds: Set<string>): Promise<Map<string, Identity>> {
const result: Map<string, Identity> = new Map()

let identities: Pick<AxiosResponse<Identity[]>, 'data' | 'headers'> = null
do {
if (!identities) {
// eslint-disable-next-line no-await-in-loop
identities = await this.identity.listIdentities({
perPage: KRATOS_LIST_PAGE_SIZE,
})
} else {
const headers = identities.headers as KratosListHeaders

const nextRel = headers.link
?.split(',')
?.find(it => it.endsWith(KRATOS_LIST_REL_NEXT))
?.trim()
if (!nextRel) {
break
}

const nextLink = nextRel.substring(1, nextRel.length - KRATOS_LIST_REL_NEXT.length - 1)
if (!nextLink) {
break
}

const url = new URL(nextLink)

// eslint-disable-next-line no-await-in-loop
identities = await this.identity.listIdentities(undefined, {
params: Object.entries(url.searchParams),
})
}

identities.data.forEach(it => {
const { id } = it
if (identityIds.has(id)) {
result.set(id, it)
identityIds.delete(id)
}
})
} while (identityIds.size > 0)

return new Map(identities.data.filter(it => ids.has(it.id)).map(it => [it.id, it]))
return result
}

async getSessionsById(id: string, activeOnly?: boolean): Promise<Session[]> {
Expand All @@ -54,6 +104,7 @@ export default class KratosService {
return [it, sessions]
}),
)

return new Map(data)
}

Expand Down Expand Up @@ -101,17 +152,6 @@ export default class KratosService {
return res.data
}

async getIdentityIdsByEmail(email: string): Promise<string[]> {
const identitites = await this.identity.listIdentities()

return identitites.data
.filter(it => {
const traits = it.traits as IdentityTraits
return traits.email.includes(email)
})
.map(it => it.id)
}

async getSessionByCookie(cookie: string): Promise<Session> {
const req = await this.frontend.toSession({
cookie,
Expand Down
2 changes: 2 additions & 0 deletions web/crux/src/shared/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ export const API_CREATED_LOCATION_HEADERS = {
}

export const UID_MAX = 2147483647

export const KRATOS_LIST_PAGE_SIZE = 128

0 comments on commit ab2711d

Please sign in to comment.