{"version":3,"file":"refresh.mjs","names":["originalCredential: Record<string, unknown>","recordId: string | undefined","refreshedCredential: Record<string, unknown>","e: any"],"sources":["../../../src/http/endpoints/refresh.ts"],"sourcesContent":["import type { Router, Response } from 'express'\nimport type { OpenBadgesHttpModuleConfig } from '../OpenBadgesHttpModuleConfig'\nimport type { OpenBadgesHttpModule } from '../OpenBadgesHttpModule'\nimport type { ObRequest } from '../router'\n\nimport { bearerAuth, requireScopes } from '../middleware/auth'\nimport { getRequestContext, sendError, sendJson } from '../router'\nimport { OpenBadgeCredentialRepository } from '../../repository/OpenBadgeCredentialRepository'\nimport { OpenBadgeCredentialRecord } from '../../repository/OpenBadgeCredentialRecord'\nimport { ProofService } from '../../services/ProofService'\nimport { KeyService } from '../../services/KeyService'\nimport { RevocationService } from '../../services/RevocationService'\nimport { VC_V2_CONTEXT, OBV3_CONTEXT } from '../../constants'\n\nconst OB_READ = 'https://purl.imsglobal.org/spec/ob/v3p0/scope/readonly'\nconst OB_WRITE = 'https://purl.imsglobal.org/spec/ob/v3p0/scope/replace'\n\n/**\n * Credential Refresh Endpoint\n *\n * Allows holders to request a refreshed (renewed) version of their credential.\n * The issuer may update validity dates or other information.\n *\n * POST /ims/ob/v3p0/credentials/refresh\n * Body: { credentialId: string } or the full credential\n *\n * Returns: refreshed credential with new proof and updated dates\n */\nexport function configureRefreshEndpoint(\n  router: Router,\n  config: OpenBadgesHttpModuleConfig,\n  module: OpenBadgesHttpModule\n) {\n  router.post(\n    config.refreshPath,\n    bearerAuth(module),\n    requireScopes([OB_WRITE]),\n    async (req: ObRequest, res: Response) => {\n      try {\n        const { agentContext } = getRequestContext(req)\n        const body = req.body as {\n          credentialId?: string\n          credential?: Record<string, unknown>\n        }\n\n        if (!body.credentialId && !body.credential) {\n          return sendError(res, 400, 'invalid_request', 'Missing credentialId or credential in request body')\n        }\n\n        const repo = agentContext.dependencyManager.resolve(OpenBadgeCredentialRepository)\n        const proofService = agentContext.dependencyManager.resolve(ProofService)\n        const keyService = agentContext.dependencyManager.resolve(KeyService)\n        const revocationService = agentContext.dependencyManager.resolve(RevocationService)\n\n        let originalCredential: Record<string, unknown>\n        let recordId: string | undefined\n\n        // Find the original credential\n        if (body.credentialId) {\n          // Look up by credential ID in stored records\n          const records = await repo.getAll(agentContext)\n          const matchingRecord = records.find((r: OpenBadgeCredentialRecord) => {\n            const cred = r.credential as Record<string, unknown>\n            return cred?.id === body.credentialId\n          })\n\n          if (!matchingRecord) {\n            return sendError(res, 404, 'not_found', `Credential not found: ${body.credentialId}`)\n          }\n\n          originalCredential = matchingRecord.credential as Record<string, unknown>\n          recordId = matchingRecord.id\n        } else {\n          originalCredential = body.credential!\n        }\n\n        // Check if credential is revoked\n        const isRevoked = await revocationService.isRevoked(agentContext, originalCredential)\n        if (isRevoked) {\n          return sendError(res, 400, 'invalid_request', 'Cannot refresh a revoked credential')\n        }\n\n        // Extract issuer info for signing\n        const issuer = originalCredential.issuer\n        const issuerId = typeof issuer === 'string' ? issuer : (issuer as any)?.id\n        if (!issuerId) {\n          return sendError(res, 400, 'invalid_request', 'Could not determine issuer from credential')\n        }\n\n        // Create refreshed credential with updated dates\n        const now = new Date()\n        const refreshedCredential: Record<string, unknown> = {\n          ...originalCredential,\n          // Update validity dates\n          validFrom: now.toISOString(),\n          // Extend validity by 1 year if original had validUntil\n          ...(originalCredential.validUntil\n            ? { validUntil: new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000).toISOString() }\n            : {}),\n          // Add refresh metadata\n          refreshService: {\n            type: 'RefreshService2023',\n            refreshedAt: now.toISOString(),\n            previousId: originalCredential.id,\n          },\n        }\n\n        // Remove old proof\n        delete refreshedCredential.proof\n\n        // Ensure contexts are correct\n        const contexts = Array.isArray(refreshedCredential['@context'])\n          ? refreshedCredential['@context']\n          : [refreshedCredential['@context']]\n        if (!contexts.includes(VC_V2_CONTEXT)) {\n          refreshedCredential['@context'] = [VC_V2_CONTEXT, ...contexts.filter((c: any) => c !== VC_V2_CONTEXT)]\n        }\n\n        // Sign the refreshed credential\n        const verificationMethod = `${issuerId}#key-1`\n        try {\n          await keyService.ensureBinding(agentContext, { controller: issuerId, vmId: verificationMethod })\n        } catch {\n          // Key binding may already exist\n        }\n\n        const signedCredential = await proofService.sign(agentContext, refreshedCredential, {\n          id: verificationMethod,\n          controller: issuerId,\n        })\n\n        // Save the refreshed credential\n        const newRecord = new OpenBadgeCredentialRecord({\n          credential: signedCredential,\n          derived: undefined,\n          status: 'unknown',\n        })\n        await repo.save(agentContext, newRecord)\n\n        // Optionally mark old credential as superseded (soft update)\n        if (recordId) {\n          try {\n            const oldRecord = await repo.getById(agentContext, recordId)\n            if (oldRecord) {\n              oldRecord.status = 'superseded'\n              await repo.update(agentContext, oldRecord)\n            }\n          } catch {\n            // Ignore update errors\n          }\n        }\n\n        agentContext.config.logger.debug('[OB][Refresh] Credential refreshed', { newRecordId: newRecord.id })\n\n        return sendJson(res, signedCredential, 200, 'application/vc+ld+json')\n      } catch (e: any) {\n        try { getRequestContext(req).agentContext.config.logger.error('[OB][Refresh] Error', { error: e?.message || String(e) }) } catch {}\n        return sendError(res, 500, 'server_error', e?.message || 'Unexpected error')\n      }\n    }\n  )\n}\n"],"mappings":"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"}