-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- WIP: Generic Issuer Mini App that allows anyone to sign Credentials.
- Loading branch information
Showing
8 changed files
with
330 additions
and
2 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<mat-tab-group> | ||
<mat-tab> | ||
<ng-template mat-tab-label> <mat-icon class="example-tab-icon">badge</mat-icon> Credentials </ng-template> | ||
<div class="padding"> | ||
<mat-card> | ||
<mat-card-content> | ||
<h1>Choose Credential to issue</h1> | ||
|
||
<p>To be done: Add a list of available schemas to choose from.</p> | ||
</mat-card-content> | ||
</mat-card> | ||
</div> | ||
</mat-tab> | ||
<mat-tab> | ||
<ng-template mat-tab-label> <mat-icon class="example-tab-icon">verified</mat-icon> Credential </ng-template> | ||
|
||
<div class="padding"> | ||
<mat-card> | ||
<mat-card-header> | ||
<mat-card-title> | ||
@if (signed) { You have signed the Voluntaryist Covenant } @else { Sign the Voluntaryist Covenant } | ||
</mat-card-title> | ||
</mat-card-header> | ||
<mat-card-content> | ||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="DID" [(ngModel)]="did" /> | ||
</mat-form-field> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="Identifier" [(ngModel)]="identifier" /> | ||
</mat-form-field> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="Name" [(ngModel)]="name" /> | ||
</mat-form-field> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="Birthdate" [(ngModel)]="birthdate" /> | ||
</mat-form-field> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="Gender" [(ngModel)]="gender" /> | ||
</mat-form-field> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="Photograph" [(ngModel)]="photograph" /> | ||
</mat-form-field> | ||
|
||
<!-- <button class="chat-action" mat-icon-button (click)="lookup()"> | ||
<mat-icon>search</mat-icon> | ||
</button> --> | ||
|
||
<p> | ||
{{ credential }} | ||
</p> | ||
</mat-card-content> | ||
<mat-card-actions> | ||
<button mat-flat-button (click)="sign()" [disabled]="loading || signed"> | ||
@if (signed) { Signed Covenant } @else { Sign Credential }</button | ||
> | ||
<!-- <button mat-button (click)="withdraw()" [disabled]="!signed">Withdraw the signature</button> --> | ||
</mat-card-actions> | ||
</mat-card> | ||
</div> | ||
</mat-tab> | ||
<mat-tab> | ||
<ng-template mat-tab-label> <mat-icon class="example-tab-icon">person_search</mat-icon> Search </ng-template> | ||
|
||
<div class="padding"> | ||
<h2>Check signing status</h2> | ||
|
||
<mat-form-field subscriptSizing="dynamic" class="did-input"> | ||
<input matInput placeholder="DID..." [(ngModel)]="did" (keydown.enter)="lookup()" /> | ||
</mat-form-field> | ||
<button class="chat-action" mat-icon-button (click)="lookup()"> | ||
<mat-icon>search</mat-icon> | ||
</button> | ||
|
||
@if (lookupSigned !== undefined) { | ||
<p> | ||
<mat-card> | ||
<mat-card-header> | ||
<mat-card-title>Signature Status</mat-card-title> | ||
</mat-card-header> | ||
<mat-card-content> | ||
<p> | ||
@if (lookupSigned) { | ||
<mat-icon class="signature-icon" inline="true">verified_user</mat-icon> | ||
} @else { | ||
<mat-icon class="signature-icon" inline="true">gpp_bad</mat-icon> | ||
} | ||
</p> | ||
<p>The identity with the following DID has @if (!lookupSigned) { not } signed the convenant:</p> | ||
<p class="ellipsis"> | ||
{{ did }} | ||
</p> | ||
</mat-card-content> | ||
</mat-card> | ||
</p> | ||
} | ||
</div> | ||
</mat-tab> | ||
</mat-tab-group> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.did-input { | ||
width: calc(100% - 40px); | ||
} | ||
|
||
.signature-icon { | ||
font-size: 4em; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { IssuerComponent } from './issuer.component'; | ||
|
||
describe('VoluntaryistCovenantComponent', () => { | ||
let component: IssuerComponent; | ||
let fixture: ComponentFixture<IssuerComponent>; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [IssuerComponent], | ||
}).compileComponents(); | ||
|
||
fixture = TestBed.createComponent(IssuerComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { Component, effect, inject } from '@angular/core'; | ||
import { MatButtonModule } from '@angular/material/button'; | ||
import { MatCardModule } from '@angular/material/card'; | ||
import { MatIconModule } from '@angular/material/icon'; | ||
import { AppService } from '../../../app.service'; | ||
import { IdentityService } from '../../../identity.service'; | ||
import { VerifiableCredential } from '@web5/credentials'; | ||
import { credential } from '../../../../protocols'; | ||
import { MatTabsModule } from '@angular/material/tabs'; | ||
import { LayoutService } from '../../../layout.service'; | ||
import { MatInputModule } from '@angular/material/input'; | ||
import { MatFormFieldModule } from '@angular/material/form-field'; | ||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
import { CommonModule } from '@angular/common'; | ||
|
||
@Component({ | ||
selector: 'app-issuer', | ||
standalone: true, | ||
imports: [ | ||
CommonModule, | ||
MatInputModule, | ||
MatFormFieldModule, | ||
FormsModule, | ||
ReactiveFormsModule, | ||
MatCardModule, | ||
MatIconModule, | ||
MatButtonModule, | ||
MatTabsModule, | ||
], | ||
templateUrl: './issuer.component.html', | ||
styleUrl: './issuer.component.scss', | ||
}) | ||
export class IssuerComponent { | ||
vcType = 'IdentityCredential'; | ||
|
||
app = inject(AppService); | ||
|
||
identity = inject(IdentityService); | ||
|
||
layout = inject(LayoutService); | ||
|
||
loading = false; | ||
|
||
signed = false; | ||
|
||
lookupSigned: boolean | undefined = undefined; | ||
|
||
did = ''; | ||
|
||
identifier = ''; | ||
|
||
name = ''; | ||
|
||
gender = ''; | ||
|
||
birthdate = ''; | ||
|
||
photograph = ''; | ||
|
||
credential = ''; | ||
|
||
constructor() { | ||
this.layout.marginOff(); | ||
|
||
effect(() => { | ||
if (this.app.initialized()) { | ||
this.load(); | ||
} | ||
}); | ||
} | ||
|
||
async load() { | ||
var { records: vcRecords } = await this.identity.web5.dwn.records.query({ | ||
message: { | ||
filter: { | ||
schema: this.vcType, | ||
dataFormat: credential.format, | ||
}, | ||
}, | ||
}); | ||
|
||
if (vcRecords!.length > 0) { | ||
this.signed = true; | ||
} | ||
} | ||
|
||
async sign() { | ||
this.loading = true; | ||
|
||
const expirationDate = new Date(); | ||
expirationDate.setFullYear(expirationDate.getFullYear() + 5); | ||
|
||
const vc = await VerifiableCredential.create({ | ||
type: this.vcType, | ||
issuer: this.identity.did, | ||
subject: this.did, | ||
data: { | ||
identifier: this.identifier, | ||
name: this.name, | ||
dateOfBirth: this.birthdate, | ||
gender: this.gender, | ||
photo: this.photograph, | ||
}, | ||
expirationDate: expirationDate.toISOString(), | ||
}); | ||
|
||
const did = await this.identity.activeAgent().did.get({ didUri: this.identity.did }); | ||
console.log('DID:', did); | ||
|
||
const identities = await this.identity.activeAgent().identity.list(); | ||
console.log('Identitites:', identities); | ||
|
||
const bearerDid = await this.identity.activeAgent().identity.get({ didUri: this.identity.did }); | ||
|
||
console.log('this.identity.did:', this.identity.did); | ||
console.log('Bearer DID:', bearerDid); | ||
console.log('Active Agent:', this.identity.activeAgent()); | ||
|
||
const vc_jwt = await vc.sign({ did: bearerDid!.did }); | ||
|
||
this.credential = vc_jwt; | ||
|
||
// const { record } = await this.identity.web5.dwn.records.create({ | ||
// data: vc_jwt, | ||
// message: { | ||
// schema: this.vcType, | ||
// dataFormat: credential.format, | ||
// published: true, | ||
// }, | ||
// }); | ||
|
||
// console.log('VC RECORD:', record); | ||
|
||
// const { status } = await record!.send(this.identity.did); | ||
// console.log('Record sent:', status, record); | ||
|
||
this.signed = true; | ||
} | ||
|
||
async lookup() { | ||
this.lookupSigned = undefined; | ||
|
||
const { records } = await this.identity.web5.dwn.records.query({ | ||
from: this.did, | ||
message: { | ||
filter: { | ||
schema: this.vcType, | ||
dataFormat: credential.format, | ||
}, | ||
}, | ||
}); | ||
|
||
console.log('VC RECORDS:', records); | ||
|
||
// TODO: Here we should actually validate the VC. | ||
if (records!.length > 0) { | ||
this.lookupSigned = true; | ||
} else { | ||
this.lookupSigned = false; | ||
} | ||
} | ||
|
||
async withdraw() { | ||
var { records: vcRecords } = await this.identity.web5.dwn.records.query({ | ||
message: { | ||
filter: { | ||
schema: this.vcType, | ||
dataFormat: credential.format, | ||
}, | ||
}, | ||
}); | ||
|
||
for (const record of vcRecords!) { | ||
await record.delete(); | ||
record.send(this.identity.did); | ||
} | ||
|
||
this.loading = false; | ||
this.signed = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters