Skip to content

Citizen PKI dashboard — RP-facing endpoints (Go-only extension)

Date: 2026-07-04. Request: template.gerege.mn/docs/EID_ENDPOINT_REQUESTS.md §B. A citizen views their own PKI through an RP's dashboard (e.g., template.gerege.mn "My system"): certificates + counts, RP-scoped activity history/counts, associated devices, dashboard totals.

Privacy — only specifically authorized RPs

These endpoints return a citizen's PII, so they are NOT OPEN to all RPs. The calling RP must have the PKI_READ permission — granted individually to each RP by an admin (admin console → Relying party (RP) → edit RP → "PKI_READ" checkbox). An unauthorized RP → 403.

  • first-party (the operator's own web/admin demo) and dev ("") — always allowed.
  • Activity is RP-scoped: an RP sees only the sessions it created (other RPs' logins/signatures are not exposed).

Authentication: as in v3, Authorization: Bearer <rp_sk_…>.

Endpoints (all require the PKI_READ permission)

GET /v3/certificates/etsi/{personEtsi}

A citizen's full set of certificates + status counts.

{
  "personEtsi": "PNOMN-...",
  "counts": { "valid": 2, "revoked": 1, "expired": 0, "suspended": 1, "total": 4 },
  "certificates": [
    { "documentNumber":"…", "type":"AUTH|SIGN|SEAL", "serialNumber":"…",
      "certificateLevel":"ADVANCED|QUALIFIED|QSCD",
      "status":"VALID|REVOKED|EXPIRED|SUSPENDED",
      "notBefore":"RFC3339", "notAfter":"RFC3339", "issuerDn":"…" }
  ]
}
Status mapping: certificates.status ACTIVE→(EXPIRED if notAfter has passed, else VALID), SUPERSEDED→SUSPENDED, REVOKED→REVOKED.

GET /v3/devices/etsi/{personEtsi}

A citizen's associated devices (active + inactive).

{
  "personEtsi":"PNOMN-...", "activeCount":1, "total":2,
  "devices":[ { "documentNumber":"…", "platform":"APNS|FCM",
                "enrolledAt":"RFC3339", "active":true, "deactivatedAt":null } ]
}

GET /v3/rp/activity/etsi/{personEtsi} — RP-scoped

History + counts of only the sessions created by the calling RP. Query: ?flow=AUTHENTICATION|SIGNATURE&limit=20&offset=0

{
  "personEtsi":"PNOMN-...",
  "counts": { "authentication": 42, "signature": 7 },
  "sessions": [ { "sessionId":"…", "flow":"AUTHENTICATION", "outcome":"OK",
                  "docText":"…", "timestamp":"RFC3339" } ],
  "total": 49, "limit": 20, "offset": 0
}
Source: audit_events (SESSION_AUTH/SESSION_SIGN, persisted across restart). RP-scoping is filtered by the audit detail's crp (creator RP UUID).

GET /v3/person/summary/etsi/{personEtsi} — dashboard totals

The dashboard's combined totals (in one call).

{
  "personEtsi":"PNOMN-...", "givenName":"…", "surname":"…",
  "certificates": { "valid":2, "revoked":1, "expired":0, "suspended":1, "total":4 },
  "activity": { "authentication":42, "signature":7 },
  "devicesActive":1, "devicesTotal":2,
  "representationCount":1
}

Well-known

GET /.well-known/eidendpoints.person.{certificates,devices,activity,summary} (marked PKI_READ).

Implementation

  • Service: service.PersonPKIService (server/internal/service/memory/person.go).
  • Handlers: server/internal/httpapi/handlers_person.go (rpAuth + requirePKIRead gate).
  • Permission: PKI_READ in the RP.Permissions CSV (checkbox in the admin console's RP form).
  • Nonexistent citizen → 404; unauthorized RP → 403; no Bearer → 401 (RequireRPAuth).