anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
350 lines • 16.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentEnabledServiceProvider = void 0;
const service_provider_1 = require("./service-provider");
const scope_validator_1 = require("../agent/scope-validator");
const service_manifest_1 = require("../agent/service-manifest");
const verification_errors_1 = require("./verification-errors");
const activity_logger_1 = require("../agent/activity/activity-logger");
const types_1 = require("../agent/activity/types");
class AgentEnabledServiceProvider extends service_provider_1.ServiceProvider {
constructor(name, did, trustedIssuers = [], options = {}) {
super(name, trustedIssuers, options);
this.agentSessions = new Map();
this.scopeValidator = new scope_validator_1.ScopeValidator();
this.requireAgentValidation = options.requireAgentValidation ?? false;
this.agentRevocationService = options.agentRevocationService;
this.activityLogger = new activity_logger_1.ActivityLogger();
// Create default service manifest if not provided
this.serviceManifest = options.serviceManifest ||
service_manifest_1.ServiceManifestBuilder.createBasicReadService(did, name);
}
setServiceManifest(manifest) {
this.serviceManifest = manifest;
}
getServiceManifest() {
return this.serviceManifest;
}
async verifyPresentation(presentation) {
// Check if this is an agent presentation
if (presentation.type?.includes('AgentPresentation')) {
return this.verifyAgentPresentation(presentation);
}
// For regular presentations, use parent class verification
const result = await super.verifyPresentation(presentation);
// If agent validation is required and this is not an agent, fail
if (this.requireAgentValidation && result.valid) {
return {
...result,
valid: false,
errors: [
...(result.errors || []),
new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.INVALID_CREDENTIAL, 'Agent validation required but presentation is not from an agent')
]
};
}
return result;
}
async verifyAgentPresentation(presentation) {
const errors = [];
const timestamp = new Date();
const startTime = Date.now();
// Extract agent info early for logging
const delegationCredential = presentation.verifiableCredential?.[0];
const agentDID = delegationCredential?.credentialSubject?.id || 'unknown';
const parentDID = delegationCredential?.credentialSubject?.parentDID || 'unknown';
try {
// Log authentication attempt
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.AUTHENTICATION, {
agentDID,
parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.SUCCESS,
scopes: [],
details: {
presentationId: presentation.id,
message: 'Agent authentication attempt started'
}
}));
// First, perform standard presentation verification
const baseResult = await super.verifyPresentation(presentation);
if (!baseResult.valid) {
// Log authentication failure
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.AUTHENTICATION, {
agentDID,
parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.FAILED,
scopes: [],
details: {
errorMessage: 'Base presentation verification failed',
errors: baseResult.errors?.map(e => e.message)
}
}));
return baseResult;
}
// Extract required scopes from manifest
const requiredScopes = this.serviceManifest.requiredScopes.map(s => s.id);
// Validate agent authorization
const agentValidation = await this.validateAgent(presentation, requiredScopes);
if (!agentValidation.isValid) {
// Log authorization failure
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.AUTHORIZATION, {
agentDID,
parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.DENIED,
scopes: requiredScopes,
details: {
errorMessage: 'Agent authorization failed',
errors: agentValidation.errors,
scopesRequested: requiredScopes,
scopesDenied: requiredScopes
}
}));
return {
valid: false,
errors: agentValidation.errors?.map(e => new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.INSUFFICIENT_PERMISSIONS, e)) || [],
timestamp
};
}
// Check revocation status if revocation service is available
if (this.agentRevocationService && agentValidation.agentDID && agentValidation.parentDID) {
const delegationCredential = presentation.verifiableCredential?.[0];
// Check if agent is revoked
const isRevoked = await this.agentRevocationService.isAgentRevoked(agentValidation.agentDID, agentValidation.parentDID);
if (isRevoked) {
return {
valid: false,
errors: [new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.CREDENTIAL_REVOKED, 'Agent has been revoked')],
timestamp
};
}
// Check service-specific revocation
const isServiceRevoked = await this.agentRevocationService.isAgentServiceRevoked(agentValidation.agentDID, agentValidation.parentDID, this.serviceManifest.serviceDID);
if (isServiceRevoked) {
return {
valid: false,
errors: [new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.CREDENTIAL_REVOKED, `Agent access to this service has been revoked`)],
timestamp
};
}
// Validate the delegation credential itself
const credentialValidation = await this.agentRevocationService.validateDelegationCredential(delegationCredential);
if (!credentialValidation.valid) {
return {
valid: false,
errors: [new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.CREDENTIAL_REVOKED, credentialValidation.reason || 'Delegation credential validation failed')],
timestamp
};
}
}
// Store agent session
if (agentValidation.agentDID && agentValidation.parentDID && agentValidation.grantedScopes) {
const sessionId = `${agentValidation.agentDID}:${Date.now()}`;
this.agentSessions.set(sessionId, {
agentDID: agentValidation.agentDID,
parentDID: agentValidation.parentDID,
scopes: agentValidation.grantedScopes,
sessionId
});
// Log successful authorization
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.AUTHORIZATION, {
agentDID: agentValidation.agentDID,
parentDID: agentValidation.parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.SUCCESS,
scopes: agentValidation.grantedScopes,
sessionId,
details: {
scopesRequested: requiredScopes,
scopesGranted: agentValidation.grantedScopes,
message: 'Agent authorization successful'
}
}));
// Log session start
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.SESSION_START, {
agentDID: agentValidation.agentDID,
parentDID: agentValidation.parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.SUCCESS,
scopes: agentValidation.grantedScopes,
sessionId,
details: {
message: 'Agent session started'
}
}));
}
// Extract delegation credential for result
const delegationCredential = presentation.verifiableCredential?.[0];
return {
valid: true,
holder: agentValidation.agentDID,
credentials: [{
id: delegationCredential.id,
issuer: delegationCredential.issuer,
type: delegationCredential.type,
attributes: {
agentDID: agentValidation.agentDID,
parentDID: agentValidation.parentDID,
scopes: agentValidation.grantedScopes,
agentName: delegationCredential.credentialSubject.name
}
}],
timestamp
};
}
catch (error) {
return {
valid: false,
errors: [
new verification_errors_1.VerificationError(verification_errors_1.VerificationErrorCode.PROCESSING_ERROR, error instanceof Error ? error.message : 'Unknown error during agent verification')
],
timestamp
};
}
}
async validateAgent(presentation, requiredScopes) {
return this.scopeValidator.validateAgentPresentation(presentation, requiredScopes, this.serviceManifest.serviceDID);
}
hasScope(agentDID, scope) {
// Check all sessions for this agent
for (const session of this.agentSessions.values()) {
if (session.agentDID === agentDID) {
return session.scopes.includes(scope);
}
}
return false;
}
getAgentScopes(agentDID) {
// Collect all scopes from all sessions for this agent
const allScopes = new Set();
for (const session of this.agentSessions.values()) {
if (session.agentDID === agentDID) {
session.scopes.forEach(scope => allScopes.add(scope));
}
}
return Array.from(allScopes);
}
getAgentParent(agentDID) {
for (const session of this.agentSessions.values()) {
if (session.agentDID === agentDID) {
return session.parentDID;
}
}
return null;
}
async revokeAgentSession(agentDID) {
let revoked = false;
for (const [sessionId, session] of this.agentSessions.entries()) {
if (session.agentDID === agentDID) {
// Log session end
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.SESSION_END, {
agentDID: session.agentDID,
parentDID: session.parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.SUCCESS,
scopes: session.scopes,
sessionId: session.sessionId,
details: {
message: 'Agent session revoked',
reason: 'Manual revocation'
}
}));
this.agentSessions.delete(sessionId);
revoked = true;
}
}
return revoked;
}
getAllAgentSessions() {
const sessions = [];
for (const [sessionId, session] of this.agentSessions.entries()) {
sessions.push({
sessionId: session.sessionId,
agentDID: session.agentDID,
parentDID: session.parentDID,
scopes: session.scopes
});
}
return sessions;
}
// Helper method to create a service manifest for this provider
static createManifestBuilder(serviceDID, name) {
return new service_manifest_1.ServiceManifestBuilder(serviceDID, name);
}
// Method to get human-readable scope descriptions
getScopeDescriptions() {
const descriptions = {};
const allScopes = [
...this.serviceManifest.requiredScopes,
...(this.serviceManifest.optionalScopes || [])
];
allScopes.forEach(scope => {
descriptions[scope.id] = scope.description;
});
return descriptions;
}
// Method to check if a set of scopes meets minimum requirements
validateScopeRequirements(grantedScopes) {
const requiredScopeIds = this.serviceManifest.requiredScopes.map(s => s.id);
const optionalScopeIds = (this.serviceManifest.optionalScopes || []).map(s => s.id);
const missingRequired = requiredScopeIds.filter(s => !grantedScopes.includes(s));
const additionalOptional = grantedScopes.filter(s => optionalScopeIds.includes(s) && !requiredScopeIds.includes(s));
return {
valid: missingRequired.length === 0,
missingRequired,
additionalOptional
};
}
// Log when an agent uses a specific scope
async logScopeUsage(agentDID, scope, details) {
// Find agent session
let agentSession = null;
for (const session of this.agentSessions.values()) {
if (session.agentDID === agentDID) {
agentSession = session;
break;
}
}
if (!agentSession) {
throw new Error('No active session for agent');
}
// Check if agent has the scope
if (!agentSession.scopes.includes(scope)) {
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.SCOPE_USAGE, {
agentDID,
parentDID: agentSession.parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: types_1.ActivityStatus.DENIED,
scopes: [scope],
sessionId: agentSession.sessionId,
details: {
...details,
errorMessage: 'Agent does not have required scope',
scopesGranted: agentSession.scopes
}
}));
throw new Error(`Agent does not have scope: ${scope}`);
}
// Log successful scope usage
await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.SCOPE_USAGE, {
agentDID,
parentDID: agentSession.parentDID,
serviceDID: this.serviceManifest.serviceDID,
status: details?.success === false ? types_1.ActivityStatus.FAILED : types_1.ActivityStatus.SUCCESS,
scopes: [scope],
sessionId: agentSession.sessionId,
details: {
...details,
message: `Agent used scope: ${scope}`
}
}));
}
// Get the activity logger instance
getActivityLogger() {
return this.activityLogger;
}
}
exports.AgentEnabledServiceProvider = AgentEnabledServiceProvider;
//# sourceMappingURL=service-provider-agent.js.map