@ajna-inc/openbadges 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/build/OpenBadgesModule.d.ts +10 -0
- package/build/OpenBadgesModule.js +75 -0
- package/build/OpenBadgesModule.js.map +1 -0
- package/build/OpenBadgesModuleConfig.d.ts +96 -0
- package/build/OpenBadgesModuleConfig.js +50 -0
- package/build/OpenBadgesModuleConfig.js.map +1 -0
- package/build/api/OpenBadgesApi.d.ts +48 -0
- package/build/api/OpenBadgesApi.js +81 -0
- package/build/api/OpenBadgesApi.js.map +1 -0
- package/build/api/index.d.ts +1 -0
- package/build/api/index.js +18 -0
- package/build/api/index.js.map +1 -0
- package/build/constants.d.ts +12 -0
- package/build/constants.js +27 -0
- package/build/constants.js.map +1 -0
- package/build/cryptosuites/EcdsaSd2023.d.ts +143 -0
- package/build/cryptosuites/EcdsaSd2023.js +518 -0
- package/build/cryptosuites/EcdsaSd2023.js.map +1 -0
- package/build/cryptosuites/EddsaRdfc2022.d.ts +112 -0
- package/build/cryptosuites/EddsaRdfc2022.js +356 -0
- package/build/cryptosuites/EddsaRdfc2022.js.map +1 -0
- package/build/cryptosuites/constants.d.ts +14 -0
- package/build/cryptosuites/constants.js +22 -0
- package/build/cryptosuites/constants.js.map +1 -0
- package/build/cryptosuites/contextPreprocessor.d.ts +24 -0
- package/build/cryptosuites/contextPreprocessor.js +127 -0
- package/build/cryptosuites/contextPreprocessor.js.map +1 -0
- package/build/cryptosuites/dataIntegrityV2Context.d.ts +144 -0
- package/build/cryptosuites/dataIntegrityV2Context.js +86 -0
- package/build/cryptosuites/dataIntegrityV2Context.js.map +1 -0
- package/build/cryptosuites/index.d.ts +11 -0
- package/build/cryptosuites/index.js +33 -0
- package/build/cryptosuites/index.js.map +1 -0
- package/build/http/OpenBadgesHttpModule.d.ts +9 -0
- package/build/http/OpenBadgesHttpModule.js +120 -0
- package/build/http/OpenBadgesHttpModule.js.map +1 -0
- package/build/http/OpenBadgesHttpModuleConfig.d.ts +55 -0
- package/build/http/OpenBadgesHttpModuleConfig.js +78 -0
- package/build/http/OpenBadgesHttpModuleConfig.js.map +1 -0
- package/build/http/endpoints/authorize.d.ts +3 -0
- package/build/http/endpoints/authorize.js +79 -0
- package/build/http/endpoints/authorize.js.map +1 -0
- package/build/http/endpoints/consent.d.ts +3 -0
- package/build/http/endpoints/consent.js +25 -0
- package/build/http/endpoints/consent.js.map +1 -0
- package/build/http/endpoints/credentials.d.ts +4 -0
- package/build/http/endpoints/credentials.js +85 -0
- package/build/http/endpoints/credentials.js.map +1 -0
- package/build/http/endpoints/did.d.ts +3 -0
- package/build/http/endpoints/did.js +48 -0
- package/build/http/endpoints/did.js.map +1 -0
- package/build/http/endpoints/introspect.d.ts +3 -0
- package/build/http/endpoints/introspect.js +37 -0
- package/build/http/endpoints/introspect.js.map +1 -0
- package/build/http/endpoints/jwks.d.ts +3 -0
- package/build/http/endpoints/jwks.js +46 -0
- package/build/http/endpoints/jwks.js.map +1 -0
- package/build/http/endpoints/profile.d.ts +4 -0
- package/build/http/endpoints/profile.js +58 -0
- package/build/http/endpoints/profile.js.map +1 -0
- package/build/http/endpoints/refresh.d.ts +15 -0
- package/build/http/endpoints/refresh.js +134 -0
- package/build/http/endpoints/refresh.js.map +1 -0
- package/build/http/endpoints/registration.d.ts +3 -0
- package/build/http/endpoints/registration.js +42 -0
- package/build/http/endpoints/registration.js.map +1 -0
- package/build/http/endpoints/revoke.d.ts +3 -0
- package/build/http/endpoints/revoke.js +38 -0
- package/build/http/endpoints/revoke.js.map +1 -0
- package/build/http/endpoints/serviceDescription.d.ts +3 -0
- package/build/http/endpoints/serviceDescription.js +52 -0
- package/build/http/endpoints/serviceDescription.js.map +1 -0
- package/build/http/endpoints/statusList.d.ts +10 -0
- package/build/http/endpoints/statusList.js +95 -0
- package/build/http/endpoints/statusList.js.map +1 -0
- package/build/http/endpoints/token.d.ts +3 -0
- package/build/http/endpoints/token.js +147 -0
- package/build/http/endpoints/token.js.map +1 -0
- package/build/http/middleware/auth.d.ts +5 -0
- package/build/http/middleware/auth.js +48 -0
- package/build/http/middleware/auth.js.map +1 -0
- package/build/http/router.d.ts +13 -0
- package/build/http/router.js +36 -0
- package/build/http/router.js.map +1 -0
- package/build/http/tenants.d.ts +2 -0
- package/build/http/tenants.js +20 -0
- package/build/http/tenants.js.map +1 -0
- package/build/http/util/auth.d.ts +8 -0
- package/build/http/util/auth.js +43 -0
- package/build/http/util/auth.js.map +1 -0
- package/build/index.d.ts +46 -0
- package/build/index.js +71 -0
- package/build/index.js.map +1 -0
- package/build/models/ClrCredential.d.ts +112 -0
- package/build/models/ClrCredential.js +52 -0
- package/build/models/ClrCredential.js.map +1 -0
- package/build/models/EndorsementCredential.d.ts +89 -0
- package/build/models/EndorsementCredential.js +11 -0
- package/build/models/EndorsementCredential.js.map +1 -0
- package/build/models/StatusListCredential.d.ts +81 -0
- package/build/models/StatusListCredential.js +28 -0
- package/build/models/StatusListCredential.js.map +1 -0
- package/build/models/index.d.ts +8 -0
- package/build/models/index.js +25 -0
- package/build/models/index.js.map +1 -0
- package/build/repository/OpenBadgeCredentialRecord.d.ts +44 -0
- package/build/repository/OpenBadgeCredentialRecord.js +46 -0
- package/build/repository/OpenBadgeCredentialRecord.js.map +1 -0
- package/build/repository/OpenBadgeCredentialRepository.d.ts +8 -0
- package/build/repository/OpenBadgeCredentialRepository.js +38 -0
- package/build/repository/OpenBadgeCredentialRepository.js.map +1 -0
- package/build/repository/OpenBadgesAuthCodeRecord.d.ts +35 -0
- package/build/repository/OpenBadgesAuthCodeRecord.js +28 -0
- package/build/repository/OpenBadgesAuthCodeRecord.js.map +1 -0
- package/build/repository/OpenBadgesAuthCodeRepository.d.ts +6 -0
- package/build/repository/OpenBadgesAuthCodeRepository.js +32 -0
- package/build/repository/OpenBadgesAuthCodeRepository.js.map +1 -0
- package/build/repository/OpenBadgesConsentRecord.d.ts +24 -0
- package/build/repository/OpenBadgesConsentRecord.js +23 -0
- package/build/repository/OpenBadgesConsentRecord.js.map +1 -0
- package/build/repository/OpenBadgesConsentRepository.d.ts +6 -0
- package/build/repository/OpenBadgesConsentRepository.js +32 -0
- package/build/repository/OpenBadgesConsentRepository.js.map +1 -0
- package/build/repository/OpenBadgesKeyBindingRecord.d.ts +24 -0
- package/build/repository/OpenBadgesKeyBindingRecord.js +32 -0
- package/build/repository/OpenBadgesKeyBindingRecord.js.map +1 -0
- package/build/repository/OpenBadgesKeyBindingRepository.d.ts +7 -0
- package/build/repository/OpenBadgesKeyBindingRepository.js +35 -0
- package/build/repository/OpenBadgesKeyBindingRepository.js.map +1 -0
- package/build/repository/OpenBadgesOAuthRecord.d.ts +35 -0
- package/build/repository/OpenBadgesOAuthRecord.js +25 -0
- package/build/repository/OpenBadgesOAuthRecord.js.map +1 -0
- package/build/repository/OpenBadgesOAuthRepository.d.ts +8 -0
- package/build/repository/OpenBadgesOAuthRepository.js +38 -0
- package/build/repository/OpenBadgesOAuthRepository.js.map +1 -0
- package/build/repository/OpenBadgesProfileRecord.d.ts +21 -0
- package/build/repository/OpenBadgesProfileRecord.js +22 -0
- package/build/repository/OpenBadgesProfileRecord.js.map +1 -0
- package/build/repository/OpenBadgesProfileRepository.d.ts +6 -0
- package/build/repository/OpenBadgesProfileRepository.js +32 -0
- package/build/repository/OpenBadgesProfileRepository.js.map +1 -0
- package/build/repository/OpenBadgesRevocationCacheRecord.d.ts +23 -0
- package/build/repository/OpenBadgesRevocationCacheRecord.js +23 -0
- package/build/repository/OpenBadgesRevocationCacheRecord.js.map +1 -0
- package/build/repository/OpenBadgesRevocationCacheRepository.d.ts +6 -0
- package/build/repository/OpenBadgesRevocationCacheRepository.js +32 -0
- package/build/repository/OpenBadgesRevocationCacheRepository.js.map +1 -0
- package/build/repository/OpenBadgesServiceDescriptionRecord.d.ts +21 -0
- package/build/repository/OpenBadgesServiceDescriptionRecord.js +22 -0
- package/build/repository/OpenBadgesServiceDescriptionRecord.js.map +1 -0
- package/build/repository/OpenBadgesServiceDescriptionRepository.d.ts +6 -0
- package/build/repository/OpenBadgesServiceDescriptionRepository.js +32 -0
- package/build/repository/OpenBadgesServiceDescriptionRepository.js.map +1 -0
- package/build/repository/OpenBadgesTokenRecord.d.ts +39 -0
- package/build/repository/OpenBadgesTokenRecord.js +36 -0
- package/build/repository/OpenBadgesTokenRecord.js.map +1 -0
- package/build/repository/OpenBadgesTokenRepository.d.ts +9 -0
- package/build/repository/OpenBadgesTokenRepository.js +45 -0
- package/build/repository/OpenBadgesTokenRepository.js.map +1 -0
- package/build/repository/StatusListRecord.d.ts +49 -0
- package/build/repository/StatusListRecord.js +47 -0
- package/build/repository/StatusListRecord.js.map +1 -0
- package/build/repository/StatusListRepository.d.ts +24 -0
- package/build/repository/StatusListRepository.js +52 -0
- package/build/repository/StatusListRepository.js.map +1 -0
- package/build/repository/index.d.ts +18 -0
- package/build/repository/index.js +35 -0
- package/build/repository/index.js.map +1 -0
- package/build/services/AchievementValidator.d.ts +158 -0
- package/build/services/AchievementValidator.js +238 -0
- package/build/services/AchievementValidator.js.map +1 -0
- package/build/services/ConsumerService.d.ts +24 -0
- package/build/services/ConsumerService.js +143 -0
- package/build/services/ConsumerService.js.map +1 -0
- package/build/services/ContextService.d.ts +14 -0
- package/build/services/ContextService.js +54 -0
- package/build/services/ContextService.js.map +1 -0
- package/build/services/DataIntegrityService.d.ts +51 -0
- package/build/services/DataIntegrityService.js +134 -0
- package/build/services/DataIntegrityService.js.map +1 -0
- package/build/services/DidCommLinkService.d.ts +7 -0
- package/build/services/DidCommLinkService.js +20 -0
- package/build/services/DidCommLinkService.js.map +1 -0
- package/build/services/DisplayMapper.d.ts +9 -0
- package/build/services/DisplayMapper.js +26 -0
- package/build/services/DisplayMapper.js.map +1 -0
- package/build/services/IssuerService.d.ts +38 -0
- package/build/services/IssuerService.js +225 -0
- package/build/services/IssuerService.js.map +1 -0
- package/build/services/JwtService.d.ts +19 -0
- package/build/services/JwtService.js +229 -0
- package/build/services/JwtService.js.map +1 -0
- package/build/services/KeyService.d.ts +102 -0
- package/build/services/KeyService.js +439 -0
- package/build/services/KeyService.js.map +1 -0
- package/build/services/OAuthClient.d.ts +26 -0
- package/build/services/OAuthClient.js +127 -0
- package/build/services/OAuthClient.js.map +1 -0
- package/build/services/ProofService.d.ts +15 -0
- package/build/services/ProofService.js +43 -0
- package/build/services/ProofService.js.map +1 -0
- package/build/services/RevocationService.d.ts +59 -0
- package/build/services/RevocationService.js +319 -0
- package/build/services/RevocationService.js.map +1 -0
- package/build/services/VerifyService.d.ts +17 -0
- package/build/services/VerifyService.js +54 -0
- package/build/services/VerifyService.js.map +1 -0
- package/build/services/crypto/CryptoDriver.d.ts +9 -0
- package/build/services/crypto/CryptoDriver.js +7 -0
- package/build/services/crypto/CryptoDriver.js.map +1 -0
- package/build/services/crypto/JsonLdCryptoDriver.d.ts +17 -0
- package/build/services/crypto/JsonLdCryptoDriver.js +45 -0
- package/build/services/crypto/JsonLdCryptoDriver.js.map +1 -0
- package/build/services/crypto/JwtCryptoDriver.d.ts +13 -0
- package/build/services/crypto/JwtCryptoDriver.js +42 -0
- package/build/services/crypto/JwtCryptoDriver.js.map +1 -0
- package/build/services/index.d.ts +12 -0
- package/build/services/index.js +29 -0
- package/build/services/index.js.map +1 -0
- package/build/utils/validate.d.ts +17 -0
- package/build/utils/validate.js +107 -0
- package/build/utils/validate.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureRefreshEndpoint = configureRefreshEndpoint;
|
|
4
|
+
const auth_1 = require("../middleware/auth");
|
|
5
|
+
const router_1 = require("../router");
|
|
6
|
+
const OpenBadgeCredentialRepository_1 = require("../../repository/OpenBadgeCredentialRepository");
|
|
7
|
+
const OpenBadgeCredentialRecord_1 = require("../../repository/OpenBadgeCredentialRecord");
|
|
8
|
+
const ProofService_1 = require("../../services/ProofService");
|
|
9
|
+
const KeyService_1 = require("../../services/KeyService");
|
|
10
|
+
const RevocationService_1 = require("../../services/RevocationService");
|
|
11
|
+
const constants_1 = require("../../constants");
|
|
12
|
+
const OB_READ = 'https://purl.imsglobal.org/spec/ob/v3p0/scope/readonly';
|
|
13
|
+
const OB_WRITE = 'https://purl.imsglobal.org/spec/ob/v3p0/scope/replace';
|
|
14
|
+
/**
|
|
15
|
+
* Credential Refresh Endpoint
|
|
16
|
+
*
|
|
17
|
+
* Allows holders to request a refreshed (renewed) version of their credential.
|
|
18
|
+
* The issuer may update validity dates or other information.
|
|
19
|
+
*
|
|
20
|
+
* POST /ims/ob/v3p0/credentials/refresh
|
|
21
|
+
* Body: { credentialId: string } or the full credential
|
|
22
|
+
*
|
|
23
|
+
* Returns: refreshed credential with new proof and updated dates
|
|
24
|
+
*/
|
|
25
|
+
function configureRefreshEndpoint(router, config, module) {
|
|
26
|
+
router.post(config.refreshPath, (0, auth_1.bearerAuth)(module), (0, auth_1.requireScopes)([OB_WRITE]), async (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
29
|
+
const body = req.body;
|
|
30
|
+
if (!body.credentialId && !body.credential) {
|
|
31
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'Missing credentialId or credential in request body');
|
|
32
|
+
}
|
|
33
|
+
const repo = agentContext.dependencyManager.resolve(OpenBadgeCredentialRepository_1.OpenBadgeCredentialRepository);
|
|
34
|
+
const proofService = agentContext.dependencyManager.resolve(ProofService_1.ProofService);
|
|
35
|
+
const keyService = agentContext.dependencyManager.resolve(KeyService_1.KeyService);
|
|
36
|
+
const revocationService = agentContext.dependencyManager.resolve(RevocationService_1.RevocationService);
|
|
37
|
+
let originalCredential;
|
|
38
|
+
let recordId;
|
|
39
|
+
// Find the original credential
|
|
40
|
+
if (body.credentialId) {
|
|
41
|
+
// Look up by credential ID in stored records
|
|
42
|
+
const records = await repo.getAll(agentContext);
|
|
43
|
+
const matchingRecord = records.find((r) => {
|
|
44
|
+
const cred = r.credential;
|
|
45
|
+
return cred?.id === body.credentialId;
|
|
46
|
+
});
|
|
47
|
+
if (!matchingRecord) {
|
|
48
|
+
return (0, router_1.sendError)(res, 404, 'not_found', `Credential not found: ${body.credentialId}`);
|
|
49
|
+
}
|
|
50
|
+
originalCredential = matchingRecord.credential;
|
|
51
|
+
recordId = matchingRecord.id;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
originalCredential = body.credential;
|
|
55
|
+
}
|
|
56
|
+
// Check if credential is revoked
|
|
57
|
+
const isRevoked = await revocationService.isRevoked(agentContext, originalCredential);
|
|
58
|
+
if (isRevoked) {
|
|
59
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'Cannot refresh a revoked credential');
|
|
60
|
+
}
|
|
61
|
+
// Extract issuer info for signing
|
|
62
|
+
const issuer = originalCredential.issuer;
|
|
63
|
+
const issuerId = typeof issuer === 'string' ? issuer : issuer?.id;
|
|
64
|
+
if (!issuerId) {
|
|
65
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'Could not determine issuer from credential');
|
|
66
|
+
}
|
|
67
|
+
// Create refreshed credential with updated dates
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const refreshedCredential = {
|
|
70
|
+
...originalCredential,
|
|
71
|
+
// Update validity dates
|
|
72
|
+
validFrom: now.toISOString(),
|
|
73
|
+
// Extend validity by 1 year if original had validUntil
|
|
74
|
+
...(originalCredential.validUntil
|
|
75
|
+
? { validUntil: new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000).toISOString() }
|
|
76
|
+
: {}),
|
|
77
|
+
// Add refresh metadata
|
|
78
|
+
refreshService: {
|
|
79
|
+
type: 'RefreshService2023',
|
|
80
|
+
refreshedAt: now.toISOString(),
|
|
81
|
+
previousId: originalCredential.id,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
// Remove old proof
|
|
85
|
+
delete refreshedCredential.proof;
|
|
86
|
+
// Ensure contexts are correct
|
|
87
|
+
const contexts = Array.isArray(refreshedCredential['@context'])
|
|
88
|
+
? refreshedCredential['@context']
|
|
89
|
+
: [refreshedCredential['@context']];
|
|
90
|
+
if (!contexts.includes(constants_1.VC_V2_CONTEXT)) {
|
|
91
|
+
refreshedCredential['@context'] = [constants_1.VC_V2_CONTEXT, ...contexts.filter((c) => c !== constants_1.VC_V2_CONTEXT)];
|
|
92
|
+
}
|
|
93
|
+
// Sign the refreshed credential
|
|
94
|
+
const verificationMethod = `${issuerId}#key-1`;
|
|
95
|
+
try {
|
|
96
|
+
await keyService.ensureBinding(agentContext, { controller: issuerId, vmId: verificationMethod });
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Key binding may already exist
|
|
100
|
+
}
|
|
101
|
+
const signedCredential = await proofService.sign(agentContext, refreshedCredential, {
|
|
102
|
+
id: verificationMethod,
|
|
103
|
+
controller: issuerId,
|
|
104
|
+
});
|
|
105
|
+
// Save the refreshed credential
|
|
106
|
+
const newRecord = new OpenBadgeCredentialRecord_1.OpenBadgeCredentialRecord({
|
|
107
|
+
credential: signedCredential,
|
|
108
|
+
derived: undefined,
|
|
109
|
+
status: 'unknown',
|
|
110
|
+
});
|
|
111
|
+
await repo.save(agentContext, newRecord);
|
|
112
|
+
// Optionally mark old credential as superseded (soft update)
|
|
113
|
+
if (recordId) {
|
|
114
|
+
try {
|
|
115
|
+
const oldRecord = await repo.getById(agentContext, recordId);
|
|
116
|
+
if (oldRecord) {
|
|
117
|
+
oldRecord.status = 'superseded';
|
|
118
|
+
await repo.update(agentContext, oldRecord);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Ignore update errors
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log('[OB][Refresh] Credential refreshed, new record id:', newRecord.id);
|
|
126
|
+
return (0, router_1.sendJson)(res, signedCredential, 200, 'application/vc+ld+json');
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
console.error('[OB][Refresh] Error:', e?.message || e);
|
|
130
|
+
return (0, router_1.sendError)(res, 500, 'server_error', e?.message || 'Unexpected error');
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../../src/http/endpoints/refresh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureRegistrationEndpoint = configureRegistrationEndpoint;
|
|
4
|
+
const router_1 = require("../router");
|
|
5
|
+
const OpenBadgesOAuthRepository_1 = require("../../repository/OpenBadgesOAuthRepository");
|
|
6
|
+
const OpenBadgesOAuthRecord_1 = require("../../repository/OpenBadgesOAuthRecord");
|
|
7
|
+
function randomId(prefix = '') {
|
|
8
|
+
return prefix + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
9
|
+
}
|
|
10
|
+
function configureRegistrationEndpoint(router, config) {
|
|
11
|
+
router.post(config.registrationPath, async (req, res) => {
|
|
12
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
13
|
+
const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository_1.OpenBadgesOAuthRepository);
|
|
14
|
+
const body = req.body || {};
|
|
15
|
+
if (!body.client_name) {
|
|
16
|
+
return (0, router_1.sendError)(res, 400, 'invalid_client_metadata', 'client_name is required');
|
|
17
|
+
}
|
|
18
|
+
const client_id = randomId('ob_');
|
|
19
|
+
const client_secret = randomId('sec_');
|
|
20
|
+
const registration = {
|
|
21
|
+
client_id,
|
|
22
|
+
client_secret,
|
|
23
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
24
|
+
client_secret_expires_at: 0,
|
|
25
|
+
token_endpoint_auth_method: 'client_secret_basic',
|
|
26
|
+
client_name: body.client_name,
|
|
27
|
+
redirect_uris: body.redirect_uris ?? [],
|
|
28
|
+
scope: body.scope ?? '',
|
|
29
|
+
grant_types: body.grant_types ?? ['authorization_code', 'refresh_token'],
|
|
30
|
+
response_types: body.response_types ?? ['code'],
|
|
31
|
+
application_type: body.application_type ?? 'web',
|
|
32
|
+
};
|
|
33
|
+
const rec = new OpenBadgesOAuthRecord_1.OpenBadgesOAuthRecord({
|
|
34
|
+
host: new URL(config.baseUrl).origin,
|
|
35
|
+
clientRegistration: registration,
|
|
36
|
+
tokens: {},
|
|
37
|
+
});
|
|
38
|
+
await oauthRepo.save(agentContext, rec);
|
|
39
|
+
(0, router_1.sendJson)(res, registration, 201);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.js","sourceRoot":"","sources":["../../../src/http/endpoints/registration.ts"],"names":[],"mappings":";;AAYA,sEAoCC;AA5CD,sCAAkE;AAClE,0FAAsF;AACtF,kFAA8E;AAE9E,SAAS,QAAQ,CAAC,MAAM,GAAG,EAAE;IAC3B,OAAO,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,SAAgB,6BAA6B,CAAC,MAAc,EAAE,MAAkC;IAC9F,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QAC3E,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;QAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qDAAyB,CAAC,CAAA;QAEnF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAA;QAC3B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,yBAAyB,EAAE,yBAAyB,CAAC,CAAA;QAClF,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;QACjC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;QAEtC,MAAM,YAAY,GAAG;YACnB,SAAS;YACT,aAAa;YACb,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YAClD,wBAAwB,EAAE,CAAC;YAC3B,0BAA0B,EAAE,qBAAqB;YACjD,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,EAAE;YACvC,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC;YACxE,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC;YAC/C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,KAAK;SACjD,CAAA;QAED,MAAM,GAAG,GAAG,IAAI,6CAAqB,CAAC;YACpC,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM;YACpC,kBAAkB,EAAE,YAAY;YAChC,MAAM,EAAE,EAAE;SACX,CAAC,CAAA;QACF,MAAM,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;QAEvC,IAAA,iBAAQ,EAAC,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureRevokeEndpoint = configureRevokeEndpoint;
|
|
4
|
+
const router_1 = require("../router");
|
|
5
|
+
const OpenBadgesTokenRepository_1 = require("../../repository/OpenBadgesTokenRepository");
|
|
6
|
+
const OpenBadgesOAuthRepository_1 = require("../../repository/OpenBadgesOAuthRepository");
|
|
7
|
+
const auth_1 = require("../util/auth");
|
|
8
|
+
function configureRevokeEndpoint(router, module) {
|
|
9
|
+
router.post(module.config.revokePath, async (req, res) => {
|
|
10
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
11
|
+
// Client auth required
|
|
12
|
+
const basic = (0, auth_1.parseBasicAuth)(req) ?? (0, auth_1.parseBodyClientAuth)(req);
|
|
13
|
+
if (!basic)
|
|
14
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Client authentication required');
|
|
15
|
+
const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository_1.OpenBadgesOAuthRepository);
|
|
16
|
+
const clientRec = await oauthRepo.findByClientId(agentContext, basic.clientId);
|
|
17
|
+
const reg = clientRec?.clientRegistration || {};
|
|
18
|
+
if (!clientRec || reg.client_secret !== basic.clientSecret)
|
|
19
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Invalid client credentials');
|
|
20
|
+
const token = String(req.body?.token ?? '');
|
|
21
|
+
if (!token)
|
|
22
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'token is required');
|
|
23
|
+
const tokenRepo = agentContext.dependencyManager.resolve(OpenBadgesTokenRepository_1.OpenBadgesTokenRepository);
|
|
24
|
+
const tokenRec = await tokenRepo.findByToken(agentContext, token);
|
|
25
|
+
if (tokenRec) {
|
|
26
|
+
if (tokenRec.clientId !== basic.clientId)
|
|
27
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'client mismatch');
|
|
28
|
+
// delete all tokens in the pair
|
|
29
|
+
const pair = await tokenRepo.findByPairId(agentContext, tokenRec.pairId);
|
|
30
|
+
for (const t of pair) {
|
|
31
|
+
await tokenRepo.deleteById(agentContext, t.id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Per RFC7009, successful revocation returns 200 even if token was unknown
|
|
35
|
+
return (0, router_1.sendJson)(res, { revoked: true });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=revoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../../src/http/endpoints/revoke.ts"],"names":[],"mappings":";;AASA,0DA4BC;AAjCD,sCAAkE;AAClE,0FAAsF;AACtF,0FAAsF;AACtF,uCAAkE;AAElE,SAAgB,uBAAuB,CAAC,MAAc,EAAE,MAA4B;IAClF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QAC5E,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;QAC/C,uBAAuB;QACvB,MAAM,KAAK,GAAG,IAAA,qBAAc,EAAC,GAAG,CAAC,IAAI,IAAA,0BAAmB,EAAC,GAAG,CAAC,CAAA;QAC7D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,EAAE,gCAAgC,CAAC,CAAA;QAC1F,MAAM,SAAS,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qDAAyB,CAAC,CAAA;QACnF,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,YAAY,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC9E,MAAM,GAAG,GAAI,SAAS,EAAE,kBAA0B,IAAI,EAAE,CAAA;QACxD,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,aAAa,KAAK,KAAK,CAAC,YAAY;YACxD,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,EAAE,4BAA4B,CAAC,CAAA;QAE5E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAA;QAC3C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,EAAE,mBAAmB,CAAC,CAAA;QAE9E,MAAM,SAAS,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qDAAyB,CAAC,CAAA;QACnF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;QACjE,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;gBAAE,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAA;YAC1G,gCAAgC;YAChC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;YACxE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,SAAS,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,CAAC,CAAA;YAChD,CAAC;QACH,CAAC;QACD,2EAA2E;QAC3E,OAAO,IAAA,iBAAQ,EAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureServiceDescriptionEndpoint = configureServiceDescriptionEndpoint;
|
|
4
|
+
const router_1 = require("../router");
|
|
5
|
+
function configureServiceDescriptionEndpoint(router, config) {
|
|
6
|
+
router.get(config.discoveryPath, async (req, res) => {
|
|
7
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
8
|
+
const base = new URL(config.baseUrl);
|
|
9
|
+
const authUrl = new URL(config.authorizePath, config.baseUrl).toString();
|
|
10
|
+
const tokenUrl = new URL(config.tokenPath, config.baseUrl).toString();
|
|
11
|
+
const registrationUrl = new URL(config.registrationPath, config.baseUrl).toString();
|
|
12
|
+
const response = {
|
|
13
|
+
openapi: '3.0.0',
|
|
14
|
+
info: {
|
|
15
|
+
title: 'Open Badges 3.0 / CLR 2.0 API',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
},
|
|
18
|
+
servers: [{ url: `${base.origin}` }],
|
|
19
|
+
components: {
|
|
20
|
+
securitySchemes: {
|
|
21
|
+
OAuth2ACG: {
|
|
22
|
+
type: 'oauth2',
|
|
23
|
+
description: 'OAuth 2.0 Authorization Code Grant authorization',
|
|
24
|
+
'x-imssf-name': config.providerName ?? agentContext.config.label ?? 'Provider',
|
|
25
|
+
'x-imssf-privacyPolicyUrl': config.privacyPolicyUrl,
|
|
26
|
+
'x-imssf-termsOfServiceUrl': config.termsOfServiceUrl,
|
|
27
|
+
'x-imssf-registrationUrl': registrationUrl,
|
|
28
|
+
'x-oauth-revocationUrl': new URL(config.revokePath, config.baseUrl).toString(),
|
|
29
|
+
'x-oauth-introspectionUrl': new URL(config.introspectPath, config.baseUrl).toString(),
|
|
30
|
+
'x-oauth-jwksUrl': new URL(config.jwksPath, config.baseUrl).toString(),
|
|
31
|
+
flows: {
|
|
32
|
+
authorizationCode: {
|
|
33
|
+
authorizationUrl: authUrl,
|
|
34
|
+
tokenUrl: tokenUrl,
|
|
35
|
+
refreshUrl: tokenUrl,
|
|
36
|
+
scopes: {
|
|
37
|
+
'https://purl.imsglobal.org/spec/ob/v3p0/scope/readonly': 'Read-only access to credentials and profile',
|
|
38
|
+
'https://purl.imsglobal.org/spec/ob/v3p0/scope/replace': 'Write/update access to credentials and profile',
|
|
39
|
+
'https://purl.imsglobal.org/spec/ob/v3p0/scope/delete': 'Delete access to credentials and profile',
|
|
40
|
+
'https://purl.imsglobal.org/spec/clr/v2p0/scope/readonly': 'Read-only CLR scope',
|
|
41
|
+
'https://purl.imsglobal.org/spec/clr/v2p0/scope/replace': 'Write/replace CLR scope',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
(0, router_1.sendJson)(res, response);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=serviceDescription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serviceDescription.js","sourceRoot":"","sources":["../../../src/http/endpoints/serviceDescription.ts"],"names":[],"mappings":";;AAMA,kFAiDC;AAnDD,sCAAuD;AAEvD,SAAgB,mCAAmC,CAAC,MAAc,EAAE,MAAkC;IACpG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QACvE,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;QAE/C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACpC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;QACxE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;QACrE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;QAEnF,MAAM,QAAQ,GAAG;YACf,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE;gBACJ,KAAK,EAAE,+BAA+B;gBACtC,OAAO,EAAE,OAAO;aACjB;YACD,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YACpC,UAAU,EAAE;gBACV,eAAe,EAAE;oBACf,SAAS,EAAE;wBACT,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,kDAAkD;wBAC/D,cAAc,EAAE,MAAM,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,UAAU;wBAC9E,0BAA0B,EAAE,MAAM,CAAC,gBAAgB;wBACnD,2BAA2B,EAAE,MAAM,CAAC,iBAAiB;wBACrD,yBAAyB,EAAE,eAAe;wBAC1C,uBAAuB,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;wBAC9E,0BAA0B,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;wBACrF,iBAAiB,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE;wBACtE,KAAK,EAAE;4BACL,iBAAiB,EAAE;gCACjB,gBAAgB,EAAE,OAAO;gCACzB,QAAQ,EAAE,QAAQ;gCAClB,UAAU,EAAE,QAAQ;gCACpB,MAAM,EAAE;oCACN,wDAAwD,EAAE,6CAA6C;oCACvG,uDAAuD,EAAE,gDAAgD;oCACzG,sDAAsD,EAAE,0CAA0C;oCAClG,yDAAyD,EAAE,qBAAqB;oCAChF,wDAAwD,EAAE,yBAAyB;iCACpF;6BACF;yBACF;qBACF;iBACF;aACF;SACF,CAAA;QAED,IAAA,iBAAQ,EAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Router } from 'express';
|
|
2
|
+
import type { OpenBadgesHttpModuleConfig } from '../OpenBadgesHttpModuleConfig';
|
|
3
|
+
import type { OpenBadgesHttpModule } from '../OpenBadgesHttpModule';
|
|
4
|
+
/**
|
|
5
|
+
* Configure the StatusList2021 endpoint
|
|
6
|
+
*
|
|
7
|
+
* GET /status-list/:id - Returns a signed StatusList2021Credential
|
|
8
|
+
* POST /status-list/:id/status - Update status for a credential index (issuer only)
|
|
9
|
+
*/
|
|
10
|
+
export declare function configureStatusListEndpoint(router: Router, config: OpenBadgesHttpModuleConfig, _module: OpenBadgesHttpModule): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureStatusListEndpoint = configureStatusListEndpoint;
|
|
4
|
+
const router_1 = require("../router");
|
|
5
|
+
const RevocationService_1 = require("../../services/RevocationService");
|
|
6
|
+
const ProofService_1 = require("../../services/ProofService");
|
|
7
|
+
/**
|
|
8
|
+
* Configure the StatusList2021 endpoint
|
|
9
|
+
*
|
|
10
|
+
* GET /status-list/:id - Returns a signed StatusList2021Credential
|
|
11
|
+
* POST /status-list/:id/status - Update status for a credential index (issuer only)
|
|
12
|
+
*/
|
|
13
|
+
function configureStatusListEndpoint(router, config, _module) {
|
|
14
|
+
const basePath = config.statusListPath;
|
|
15
|
+
// GET /status-list/:id - Return signed StatusList2021Credential
|
|
16
|
+
router.get(`${basePath}/:id`, async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
19
|
+
const listId = String(req.params.id);
|
|
20
|
+
const revocationService = agentContext.dependencyManager.resolve(RevocationService_1.RevocationService);
|
|
21
|
+
const proofService = agentContext.dependencyManager.resolve(ProofService_1.ProofService);
|
|
22
|
+
// Get the status list record
|
|
23
|
+
const record = await revocationService.getStatusList(agentContext, listId);
|
|
24
|
+
if (!record) {
|
|
25
|
+
return (0, router_1.sendError)(res, 404, 'not_found', `Status list not found: ${listId}`);
|
|
26
|
+
}
|
|
27
|
+
// Build the unsigned StatusList2021Credential
|
|
28
|
+
const unsignedCredential = revocationService.buildStatusListCredential(record);
|
|
29
|
+
// Sign the credential
|
|
30
|
+
const verificationMethod = `${record.issuerDid}#key-1`;
|
|
31
|
+
const signedCredential = await proofService.sign(agentContext, unsignedCredential, {
|
|
32
|
+
id: verificationMethod,
|
|
33
|
+
controller: record.issuerDid,
|
|
34
|
+
});
|
|
35
|
+
// Return with proper content type
|
|
36
|
+
return (0, router_1.sendJson)(res, signedCredential, 200, 'application/vc+ld+json');
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.error('[OB][StatusList] GET error:', e?.message || e);
|
|
40
|
+
return (0, router_1.sendError)(res, 500, 'server_error', e?.message || 'Unexpected error');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// POST /status-list/:id/status - Update status for an index (issuer operation)
|
|
44
|
+
router.post(`${basePath}/:id/status`, async (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
47
|
+
const listId = String(req.params.id);
|
|
48
|
+
const { index, status } = req.body;
|
|
49
|
+
if (typeof index !== 'number' || typeof status !== 'boolean') {
|
|
50
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'Missing required fields: index (number), status (boolean)');
|
|
51
|
+
}
|
|
52
|
+
const revocationService = agentContext.dependencyManager.resolve(RevocationService_1.RevocationService);
|
|
53
|
+
// Update the status
|
|
54
|
+
await revocationService.setStatus(agentContext, { listId, index, status });
|
|
55
|
+
return (0, router_1.sendJson)(res, { success: true, listId, index, status: status ? 'revoked' : 'active' });
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error('[OB][StatusList] POST error:', e?.message || e);
|
|
59
|
+
if (e?.message?.includes('not found')) {
|
|
60
|
+
return (0, router_1.sendError)(res, 404, 'not_found', e.message);
|
|
61
|
+
}
|
|
62
|
+
return (0, router_1.sendError)(res, 500, 'server_error', e?.message || 'Unexpected error');
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// POST /status-list - Create a new status list
|
|
66
|
+
router.post(basePath, async (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
69
|
+
const { listId, issuerDid, purpose, capacity } = req.body;
|
|
70
|
+
if (!listId || !issuerDid) {
|
|
71
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'Missing required fields: listId, issuerDid');
|
|
72
|
+
}
|
|
73
|
+
const revocationService = agentContext.dependencyManager.resolve(RevocationService_1.RevocationService);
|
|
74
|
+
const record = await revocationService.createStatusList(agentContext, {
|
|
75
|
+
listId,
|
|
76
|
+
issuerDid,
|
|
77
|
+
purpose: purpose || 'revocation',
|
|
78
|
+
capacity,
|
|
79
|
+
baseUrl: config.baseUrl,
|
|
80
|
+
});
|
|
81
|
+
return (0, router_1.sendJson)(res, {
|
|
82
|
+
success: true,
|
|
83
|
+
listId: record.listId,
|
|
84
|
+
purpose: record.purpose,
|
|
85
|
+
capacity: record.capacity,
|
|
86
|
+
statusListCredentialUrl: `${config.baseUrl}${basePath}/${listId}`,
|
|
87
|
+
}, 201);
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.error('[OB][StatusList] POST create error:', e?.message || e);
|
|
91
|
+
return (0, router_1.sendError)(res, 500, 'server_error', e?.message || 'Unexpected error');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=statusList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusList.js","sourceRoot":"","sources":["../../../src/http/endpoints/statusList.ts"],"names":[],"mappings":";;AAeA,kEAuGC;AAjHD,sCAAkE;AAClE,wEAAoE;AACpE,8DAA0D;AAE1D;;;;;GAKG;AACH,SAAgB,2BAA2B,CACzC,MAAc,EACd,MAAkC,EAClC,OAA6B;IAE7B,MAAM,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAA;IAEtC,gEAAgE;IAChE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,MAAM,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QACpE,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;YAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAEpC,MAAM,iBAAiB,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qCAAiB,CAAC,CAAA;YACnF,MAAM,YAAY,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,2BAAY,CAAC,CAAA;YAEzE,6BAA6B;YAC7B,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;YAC1E,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,0BAA0B,MAAM,EAAE,CAAC,CAAA;YAC7E,CAAC;YAED,8CAA8C;YAC9C,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAA;YAE9E,sBAAsB;YACtB,MAAM,kBAAkB,GAAG,GAAG,MAAM,CAAC,SAAS,QAAQ,CAAA;YACtD,MAAM,gBAAgB,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,kBAAkB,EAAE;gBACjF,EAAE,EAAE,kBAAkB;gBACtB,UAAU,EAAE,MAAM,CAAC,SAAS;aAC7B,CAAC,CAAA;YAEF,kCAAkC;YAClC,OAAO,IAAA,iBAAQ,EAAC,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;YAC7D,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,IAAI,kBAAkB,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,+EAA+E;IAC/E,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,aAAa,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QAC5E,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;YAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACpC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,IAA4C,CAAA;YAE1E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7D,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,EAAE,2DAA2D,CAAC,CAAA;YAC5G,CAAC;YAED,MAAM,iBAAiB,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qCAAiB,CAAC,CAAA;YAEnF,oBAAoB;YACpB,MAAM,iBAAiB,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;YAE1E,OAAO,IAAA,iBAAQ,EAAC,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC/F,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;YAC9D,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtC,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;YACpD,CAAC;YACD,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,IAAI,kBAAkB,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,+CAA+C;IAC/C,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAc,EAAE,GAAa,EAAE,EAAE;QAC5D,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,GAAG,IAAA,0BAAiB,EAAC,GAAG,CAAC,CAAA;YAC/C,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC,IAKpD,CAAA;YAED,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;gBAC1B,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,EAAE,4CAA4C,CAAC,CAAA;YAC7F,CAAC;YAED,MAAM,iBAAiB,GAAG,YAAY,CAAC,iBAAiB,CAAC,OAAO,CAAC,qCAAiB,CAAC,CAAA;YAEnF,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,gBAAgB,CAAC,YAAY,EAAE;gBACpE,MAAM;gBACN,SAAS;gBACT,OAAO,EAAE,OAAO,IAAI,YAAY;gBAChC,QAAQ;gBACR,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,CAAC,CAAA;YAEF,OAAO,IAAA,iBAAQ,EAAC,GAAG,EAAE;gBACnB,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,uBAAuB,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,QAAQ,IAAI,MAAM,EAAE;aAClE,EAAE,GAAG,CAAC,CAAA;QACT,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;YACrE,OAAO,IAAA,kBAAS,EAAC,GAAG,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,IAAI,kBAAkB,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configureTokenEndpoint = configureTokenEndpoint;
|
|
4
|
+
const router_1 = require("../router");
|
|
5
|
+
const OpenBadgesAuthCodeRepository_1 = require("../../repository/OpenBadgesAuthCodeRepository");
|
|
6
|
+
const OpenBadgesTokenRepository_1 = require("../../repository/OpenBadgesTokenRepository");
|
|
7
|
+
const OpenBadgesTokenRecord_1 = require("../../repository/OpenBadgesTokenRecord");
|
|
8
|
+
const OpenBadgesOAuthRepository_1 = require("../../repository/OpenBadgesOAuthRepository");
|
|
9
|
+
const auth_1 = require("../util/auth");
|
|
10
|
+
function randomToken(prefix = '') {
|
|
11
|
+
return prefix + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
12
|
+
}
|
|
13
|
+
function configureTokenEndpoint(router, module) {
|
|
14
|
+
router.post(module.config.tokenPath, async (req, res) => {
|
|
15
|
+
const { agentContext } = (0, router_1.getRequestContext)(req);
|
|
16
|
+
const grant_type = String(req.body?.grant_type ?? '');
|
|
17
|
+
if (grant_type === 'authorization_code') {
|
|
18
|
+
// Client auth
|
|
19
|
+
const basic = (0, auth_1.parseBasicAuth)(req) ?? (0, auth_1.parseBodyClientAuth)(req);
|
|
20
|
+
if (!basic)
|
|
21
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Client authentication required');
|
|
22
|
+
const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository_1.OpenBadgesOAuthRepository);
|
|
23
|
+
const clientRec = await oauthRepo.findByClientId(agentContext, basic.clientId);
|
|
24
|
+
const reg = clientRec?.clientRegistration || {};
|
|
25
|
+
if (!clientRec || reg.client_secret !== basic.clientSecret)
|
|
26
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Invalid client credentials');
|
|
27
|
+
const code = String(req.body?.code ?? '');
|
|
28
|
+
const redirect_uri = String(req.body?.redirect_uri ?? '');
|
|
29
|
+
const code_verifier = req.body?.code_verifier ? String(req.body.code_verifier) : undefined;
|
|
30
|
+
if (!code || !redirect_uri)
|
|
31
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'code and redirect_uri required');
|
|
32
|
+
const codeRepo = agentContext.dependencyManager.resolve(OpenBadgesAuthCodeRepository_1.OpenBadgesAuthCodeRepository);
|
|
33
|
+
const entry = await codeRepo.findByCode(agentContext, code);
|
|
34
|
+
if (!entry || entry.expiresAt.getTime() < Date.now())
|
|
35
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'Invalid or expired code');
|
|
36
|
+
if (entry.redirectUri !== redirect_uri)
|
|
37
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'redirect_uri mismatch');
|
|
38
|
+
if (entry.clientId !== basic.clientId)
|
|
39
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'client mismatch');
|
|
40
|
+
// PKCE check when a code_challenge was provided
|
|
41
|
+
if (entry.codeChallenge) {
|
|
42
|
+
if (!code_verifier)
|
|
43
|
+
return (0, router_1.sendError)(res, 400, 'invalid_request', 'code_verifier required');
|
|
44
|
+
const { createHash } = require('crypto');
|
|
45
|
+
const b64url = (buf) => buf
|
|
46
|
+
.toString('base64')
|
|
47
|
+
.replace(/=/g, '')
|
|
48
|
+
.replace(/\+/g, '-')
|
|
49
|
+
.replace(/\//g, '_');
|
|
50
|
+
if (entry.codeChallengeMethod === 'S256') {
|
|
51
|
+
const expected = b64url(createHash('sha256').update(code_verifier).digest());
|
|
52
|
+
if (expected !== entry.codeChallenge)
|
|
53
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'PKCE verification failed');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// plain
|
|
57
|
+
if (code_verifier !== entry.codeChallenge)
|
|
58
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'PKCE verification failed');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const accessToken = randomToken('at_');
|
|
62
|
+
const refreshToken = randomToken('rt_');
|
|
63
|
+
const expiresIn = 3 * 60;
|
|
64
|
+
const pairId = randomToken('pair_');
|
|
65
|
+
const tokenRepo = agentContext.dependencyManager.resolve(OpenBadgesTokenRepository_1.OpenBadgesTokenRepository);
|
|
66
|
+
await tokenRepo.save(agentContext, new OpenBadgesTokenRecord_1.OpenBadgesTokenRecord({
|
|
67
|
+
token: accessToken,
|
|
68
|
+
tokenType: 'access',
|
|
69
|
+
clientId: entry.clientId,
|
|
70
|
+
subject: entry.subject,
|
|
71
|
+
scope: entry.scope,
|
|
72
|
+
expiresAt: new Date(Date.now() + expiresIn * 1000),
|
|
73
|
+
pairId,
|
|
74
|
+
host: new URL(module.config.baseUrl).origin,
|
|
75
|
+
}));
|
|
76
|
+
await tokenRepo.save(agentContext, new OpenBadgesTokenRecord_1.OpenBadgesTokenRecord({
|
|
77
|
+
token: refreshToken,
|
|
78
|
+
tokenType: 'refresh',
|
|
79
|
+
clientId: entry.clientId,
|
|
80
|
+
subject: entry.subject,
|
|
81
|
+
scope: entry.scope,
|
|
82
|
+
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
|
|
83
|
+
pairId,
|
|
84
|
+
host: new URL(module.config.baseUrl).origin,
|
|
85
|
+
}));
|
|
86
|
+
await codeRepo.deleteById(agentContext, entry.id);
|
|
87
|
+
return (0, router_1.sendJson)(res, {
|
|
88
|
+
access_token: accessToken,
|
|
89
|
+
token_type: 'Bearer',
|
|
90
|
+
expires_in: expiresIn,
|
|
91
|
+
refresh_token: refreshToken,
|
|
92
|
+
scope: entry.scope,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (grant_type === 'refresh_token') {
|
|
96
|
+
const basic = (0, auth_1.parseBasicAuth)(req) ?? (0, auth_1.parseBodyClientAuth)(req);
|
|
97
|
+
if (!basic)
|
|
98
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Client authentication required');
|
|
99
|
+
const oauthRepo = agentContext.dependencyManager.resolve(OpenBadgesOAuthRepository_1.OpenBadgesOAuthRepository);
|
|
100
|
+
const clientRec = await oauthRepo.findByClientId(agentContext, basic.clientId);
|
|
101
|
+
const reg = clientRec?.clientRegistration || {};
|
|
102
|
+
if (!clientRec || reg.client_secret !== basic.clientSecret)
|
|
103
|
+
return (0, router_1.sendError)(res, 401, 'invalid_client', 'Invalid client credentials');
|
|
104
|
+
const refresh_token = String(req.body?.refresh_token ?? '');
|
|
105
|
+
const tokenRepo = agentContext.dependencyManager.resolve(OpenBadgesTokenRepository_1.OpenBadgesTokenRepository);
|
|
106
|
+
const stored = await tokenRepo.findByToken(agentContext, refresh_token);
|
|
107
|
+
if (!stored || stored.tokenType !== 'refresh' || stored.expiresAt.getTime() < Date.now())
|
|
108
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'Invalid or expired refresh token');
|
|
109
|
+
if (stored.clientId !== basic.clientId)
|
|
110
|
+
return (0, router_1.sendError)(res, 400, 'invalid_grant', 'client mismatch');
|
|
111
|
+
const accessToken = randomToken('at_');
|
|
112
|
+
const newRefreshToken = randomToken('rt_');
|
|
113
|
+
const expiresIn = 3 * 60;
|
|
114
|
+
await tokenRepo.save(agentContext, new OpenBadgesTokenRecord_1.OpenBadgesTokenRecord({
|
|
115
|
+
token: accessToken,
|
|
116
|
+
tokenType: 'access',
|
|
117
|
+
clientId: stored.clientId,
|
|
118
|
+
subject: stored.subject,
|
|
119
|
+
scope: stored.scope,
|
|
120
|
+
expiresAt: new Date(Date.now() + expiresIn * 1000),
|
|
121
|
+
pairId: stored.pairId,
|
|
122
|
+
host: stored.host,
|
|
123
|
+
}));
|
|
124
|
+
// Rotate refresh token: delete old, create new one
|
|
125
|
+
await tokenRepo.deleteById(agentContext, stored.id);
|
|
126
|
+
await tokenRepo.save(agentContext, new OpenBadgesTokenRecord_1.OpenBadgesTokenRecord({
|
|
127
|
+
token: newRefreshToken,
|
|
128
|
+
tokenType: 'refresh',
|
|
129
|
+
clientId: stored.clientId,
|
|
130
|
+
subject: stored.subject,
|
|
131
|
+
scope: stored.scope,
|
|
132
|
+
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000),
|
|
133
|
+
pairId: stored.pairId,
|
|
134
|
+
host: stored.host,
|
|
135
|
+
}));
|
|
136
|
+
return (0, router_1.sendJson)(res, {
|
|
137
|
+
access_token: accessToken,
|
|
138
|
+
token_type: 'Bearer',
|
|
139
|
+
expires_in: expiresIn,
|
|
140
|
+
refresh_token: newRefreshToken,
|
|
141
|
+
scope: stored.scope,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return (0, router_1.sendError)(res, 400, 'unsupported_grant_type', 'Only authorization_code and refresh_token supported');
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/http/endpoints/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NextFunction, Response } from 'express';
|
|
2
|
+
import type { ObRequest } from '../router';
|
|
3
|
+
import type { OpenBadgesHttpModule } from '../OpenBadgesHttpModule';
|
|
4
|
+
export declare function bearerAuth(module: OpenBadgesHttpModule): (req: ObRequest, res: Response, next: NextFunction) => Promise<void>;
|
|
5
|
+
export declare function requireScopes(required: string[]): (req: ObRequest, res: Response, next: NextFunction) => void;
|