Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,14 @@ jobs:
- name: Run ${{ matrix.test-group }} tests
run: |
if [ "${{ matrix.test-group }}" = "integration" ]; then
cd contracts && cargo test --test '*' --release
# `cargo test --test '*'` errors when no integration test targets
# exist (no contracts/tests/ directory), so only run it when there
# is at least one *.rs integration test file.
if ls contracts/tests/*.rs >/dev/null 2>&1; then
cd contracts && cargo test --test '*' --release
else
echo "No integration test targets in contracts/tests/; skipping."
fi
else
cd contracts && cargo test --lib --release
fi
Expand Down
10 changes: 5 additions & 5 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@
import mongoose from 'mongoose';
import { MigrationRunner, createPool } from './utils/migrate';
import * as path from 'path';
// @ts-ignore

Check failure on line 32 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import SecureRealtimeCommunication from './services/secureRealtimeCommunication';
import { swaggerSpec } from './config/swagger';
import { openApiSpec } from './docs/openapi';
import { Migrator } from './utils/migrate';

// @ts-ignore

Check failure on line 38 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import * as transactionQueue from './services/transactionQueue';
// @ts-ignore

Check failure on line 40 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import * as transactionProcessor from './workers/transactionProcessor';
// @ts-ignore

Check failure on line 42 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import * as transactionEvents from './events/transactionEvents';

// Bridge relayer monitor watch job — Issue #423
Expand All @@ -64,9 +64,9 @@
securityHeadersMiddleware
} from './middleware/security';
import { detectSuspiciousPatterns } from './middleware/sanitizer';
// @ts-ignore - CommonJS module without type declarations

Check failure on line 67 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import { validateFileUpload } from './middleware/sanitizeMiddleware';
// @ts-ignore

Check failure on line 69 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
import { tieredRateLimiter, transactionLimiter } from './middleware/rateLimiter';
import { rateLimits } from './middleware/rateLimit';
import { idempotency } from './middleware/idempotency';
Expand Down Expand Up @@ -95,7 +95,7 @@
};

// Import routes
// @ts-ignore

Check failure on line 98 in backend/src/index.ts

View workflow job for this annotation

GitHub Actions / Build Backend

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
const quizRoutes = loadRoute('./routes/quizRoutes');
// @ts-ignore
const questionGenerationRoutes = loadRoute('./routes/questionGen');
Expand Down Expand Up @@ -150,9 +150,9 @@
// @ts-ignore
const jobRoutes = loadRoute('./routes/jobRoutes');

// Public verification route
// DID registry routes — Issue #397
// @ts-ignore
const verifyRoutes = loadRoute('./routes/verify');
const didRoutes = loadRoute('./routes/did');

// Initialize Express app
const app: Application = express();
Expand Down Expand Up @@ -369,8 +369,8 @@
// Background job management routes — Issue #258
app.use('/api/jobs', jobRoutes);

// Engagement-aware content adaptation — Issue #408
app.use('/api/adaptation', adaptationRoutes);
// DID registry — Issue #397
app.use('/api/did', didRoutes);

// Root endpoint
// ── Versioned API routes (/api/v1/*) ────────────────────────────────────────
Expand Down Expand Up @@ -410,7 +410,7 @@
app.use('/api/v1/vrf', vrfRoutes);
app.use('/api/v1/translate', translationRoutes);
app.use('/api/v1/localization', localizationRoutes);
app.use('/api/v1/adaptation', adaptationRoutes);
app.use('/api/v1/did', didRoutes);
app.use('/api/v1/cross-protocol-bridge', crossProtocolBridgeRoutes);
app.use('/api/v1/audit', auditRoutes);
app.use('/api/v1/verify', verifyRoutes);
Expand Down
80 changes: 80 additions & 0 deletions backend/src/models/Identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import mongoose, { Document, Schema } from 'mongoose';

/**
* Identity model — Issue #397 (self-sovereign identity / DID).
*
* Off-chain index that links a learner's Stellar wallet to their
* decentralized identifier (`did:aethermint:<wallet>`), mirrors the current
* verification key and rotation history, and records which on-chain
* credentials have been issued to the DID's holder.
*
* The on-chain DID registry contract (`contracts/src/did_registry.rs`) is the
* authoritative registry; this model is the API-side mirror that keeps
* resolution, wallet lookups, and credential linkage fast and queryable.
*/

/** One entry in a DID's key-rotation history (mirrors `KeyRotationRecord`). */
export interface KeyRotationRecord {
/** Verification key in use before the rotation (hex, 64 chars). */
oldKey: string;
/** Verification key in use after the rotation (hex, 64 chars). */
newKey: string;
/** Unix timestamp (seconds) of the rotation. */
rotatedAt: number;
/** Wallet address that performed the rotation (the DID controller). */
rotatedBy: string;
}

export interface Identity {
/** Decentralized identifier, e.g. `did:aethermint:GABCDE...`. */
did: string;
/** Stellar wallet that controls the DID. Stable across key rotations. */
controller: string;
/** Optional link to the platform user (`User._id`). */
userId?: string;
/** Current ed25519 verification key (hex, 64 chars). */
verificationKey: string;
/** Monotonic key version; bumped on every rotation. */
keyVersion: number;
/** Whether the DID is active and may be used for verification. */
active: boolean;
/** Credential IDs issued to the DID's holder (on-chain credential registry). */
credentialIds: number[];
/** Full rotation history (old key preserved for auditability). */
keyHistory: KeyRotationRecord[];
createdAt: Date;
updatedAt: Date;
}

export interface IIdentityDocument extends Document, Identity {}

const KeyRotationRecordSchema = new Schema<KeyRotationRecord>(
{
oldKey: { type: String, required: true },
newKey: { type: String, required: true },
rotatedAt: { type: Number, required: true },
rotatedBy: { type: String, required: true },
},
{ _id: false }
);

const IdentitySchema = new Schema<IIdentityDocument>(
{
did: { type: String, required: true },
controller: { type: String, required: true },
userId: { type: String },
verificationKey: { type: String, required: true },
keyVersion: { type: Number, required: true, default: 1 },
active: { type: Boolean, required: true, default: true },
credentialIds: { type: [Number], default: [] },
keyHistory: { type: [KeyRotationRecordSchema], default: [] },
},
{ timestamps: true }
);

// One DID per wallet, and one wallet per DID.
IdentitySchema.index({ did: 1 }, { unique: true });
IdentitySchema.index({ controller: 1 }, { unique: true });
IdentitySchema.index({ userId: 1 }, { sparse: true });

export const IdentityModel = mongoose.model<IIdentityDocument>('Identity', IdentitySchema);
Loading
Loading