{"version":3,"file":"token.mjs","names":[],"sources":["../../../src/http/endpoints/token.ts"],"sourcesContent":["import type { Router, Response } from 'express'\nimport type { OpenBadgesHttpModule } from '../OpenBadgesHttpModule'\nimport type { ObRequest } from '../router'\n\nimport { sendError, sendJson, getRequestContext } from '../router'\nimport { Hasher, TypedArrayEncoder } from '@credo-ts/core'\nimport { OpenBadgesAuthCodeRepository } from '../../repository/OpenBadgesAuthCodeRepository'\nimport { OpenBadgesTokenRepository } from '../../repository/OpenBadgesTokenRepository'\nimport { OpenBadgesTokenRecord } from '../../repository/OpenBadgesTokenRecord'\nimport { OpenBadgesOAuthRepository } from '../../repository/OpenBadgesOAuthRepository'\nimport { parseBasicAuth, parseBodyClientAuth } from '../util/auth'\n\nfunction randomToken(prefix = '') {\n  return prefix + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)\n}\n\nexport function configureTokenEndpoint(router: Router, module: OpenBadgesHttpModule) {\n  router.post(module.config.tokenPath, async (req: ObRequest, res: Response) => {\n    const { agentContext } = getRequestContext(req)\n    const grant_type = String(req.body?.grant_type ?? '')\n\n    if (grant_type === 'authorization_code') {\n      // Client auth\n      const basic = parseBasicAuth(req) ?? parseBodyClientAuth(req)\n      if (!basic) return sendError(res, 401, 'invalid_client', 'Client authentication required')\n      const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository)\n      const clientRec = await oauthRepo.findByClientId(agentContext, basic.clientId)\n      const reg = (clientRec?.clientRegistration as any) || {}\n      if (!clientRec || reg.client_secret !== basic.clientSecret)\n        return sendError(res, 401, 'invalid_client', 'Invalid client credentials')\n\n      const code = String(req.body?.code ?? '')\n      const redirect_uri = String(req.body?.redirect_uri ?? '')\n      const code_verifier = req.body?.code_verifier ? String(req.body.code_verifier) : undefined\n      if (!code || !redirect_uri) return sendError(res, 400, 'invalid_request', 'code and redirect_uri required')\n\n      const codeRepo = agentContext.dependencyManager.resolve(OpenBadgesAuthCodeRepository)\n      const entry = await codeRepo.findByCode(agentContext, code)\n      if (!entry || entry.expiresAt.getTime() < Date.now()) return sendError(res, 400, 'invalid_grant', 'Invalid or expired code')\n      if (entry.redirectUri !== redirect_uri) return sendError(res, 400, 'invalid_grant', 'redirect_uri mismatch')\n      if (entry.clientId !== basic.clientId) return sendError(res, 400, 'invalid_grant', 'client mismatch')\n\n      // PKCE check when a code_challenge was provided\n      if (entry.codeChallenge) {\n        if (!code_verifier) return sendError(res, 400, 'invalid_request', 'code_verifier required')\n        if (entry.codeChallengeMethod === 'S256') {\n          // Use cross-platform Hasher instead of Node crypto\n          const hashBytes = Hasher.hash(TypedArrayEncoder.fromString(code_verifier), 'sha-256')\n          const expected = TypedArrayEncoder.toBase64URL(hashBytes)\n          if (expected !== entry.codeChallenge) return sendError(res, 400, 'invalid_grant', 'PKCE verification failed')\n        } else {\n          // plain\n          if (code_verifier !== entry.codeChallenge) return sendError(res, 400, 'invalid_grant', 'PKCE verification failed')\n        }\n      }\n\n      const accessToken = randomToken('at_')\n      const refreshToken = randomToken('rt_')\n      const expiresIn = 3 * 60\n      const pairId = randomToken('pair_')\n      const tokenRepo = agentContext.dependencyManager.resolve(OpenBadgesTokenRepository)\n      await tokenRepo.save(\n        agentContext,\n        new OpenBadgesTokenRecord({\n          token: accessToken,\n          tokenType: 'access',\n          clientId: entry.clientId,\n          subject: entry.subject,\n          scope: entry.scope,\n          expiresAt: new Date(Date.now() + expiresIn * 1000),\n          pairId,\n          host: new URL(module.config.baseUrl).origin,\n        })\n      )\n      await tokenRepo.save(\n        agentContext,\n        new OpenBadgesTokenRecord({\n          token: refreshToken,\n          tokenType: 'refresh',\n          clientId: entry.clientId,\n          subject: entry.subject,\n          scope: entry.scope,\n          expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),\n          pairId,\n          host: new URL(module.config.baseUrl).origin,\n        })\n      )\n      await codeRepo.deleteById(agentContext, entry.id)\n\n      return sendJson(res, {\n        access_token: accessToken,\n        token_type: 'Bearer',\n        expires_in: expiresIn,\n        refresh_token: refreshToken,\n        scope: entry.scope,\n      })\n    }\n\n    if (grant_type === 'refresh_token') {\n      const basic = parseBasicAuth(req) ?? parseBodyClientAuth(req)\n      if (!basic) return sendError(res, 401, 'invalid_client', 'Client authentication required')\n      const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository)\n      const clientRec = await oauthRepo.findByClientId(agentContext, basic.clientId)\n      const reg = (clientRec?.clientRegistration as any) || {}\n      if (!clientRec || reg.client_secret !== basic.clientSecret)\n        return sendError(res, 401, 'invalid_client', 'Invalid client credentials')\n\n      const refresh_token = String(req.body?.refresh_token ?? '')\n      const tokenRepo = agentContext.dependencyManager.resolve(OpenBadgesTokenRepository)\n      const stored = await tokenRepo.findByToken(agentContext, refresh_token)\n      if (!stored || stored.tokenType !== 'refresh' || stored.expiresAt.getTime() < Date.now())\n        return sendError(res, 400, 'invalid_grant', 'Invalid or expired refresh token')\n      if (stored.clientId !== basic.clientId) return sendError(res, 400, 'invalid_grant', 'client mismatch')\n\n      const accessToken = randomToken('at_')\n      const newRefreshToken = randomToken('rt_')\n      const expiresIn = 3 * 60\n      await tokenRepo.save(\n        agentContext,\n        new OpenBadgesTokenRecord({\n          token: accessToken,\n          tokenType: 'access',\n          clientId: stored.clientId,\n          subject: stored.subject,\n          scope: stored.scope,\n          expiresAt: new Date(Date.now() + expiresIn * 1000),\n          pairId: stored.pairId,\n          host: stored.host,\n        })\n      )\n      // Rotate refresh token: delete old, create new one\n      await tokenRepo.deleteById(agentContext, stored.id)\n      await tokenRepo.save(\n        agentContext,\n        new OpenBadgesTokenRecord({\n          token: newRefreshToken,\n          tokenType: 'refresh',\n          clientId: stored.clientId,\n          subject: stored.subject,\n          scope: stored.scope,\n          expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),\n          pairId: stored.pairId,\n          host: stored.host,\n        })\n      )\n\n      return sendJson(res, {\n        access_token: accessToken,\n        token_type: 'Bearer',\n        expires_in: expiresIn,\n        refresh_token: newRefreshToken,\n        scope: stored.scope,\n      })\n    }\n\n    return sendError(res, 400, 'unsupported_grant_type', 'Only authorization_code and refresh_token supported')\n  })\n}\n"],"mappings":"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"}