Skip to content

Commit

Permalink
Add Issuer Mini App
Browse files Browse the repository at this point in the history
- WIP: Generic Issuer Mini App that allows anyone to sign Credentials.
  • Loading branch information
sondreb committed Oct 29, 2024
1 parent f9d3639 commit ccdc0c9
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 2 deletions.
Binary file added app/public/icons/apps/issuer.webp
Binary file not shown.
6 changes: 6 additions & 0 deletions app/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ export const routes: Routes = [
title: 'Voluntaryist Covenant',
data: { hide: true, icon: 'assured_workload' },
},
{
path: 'app/issuer',
loadComponent: () => import('./apps/app/issuer/issuer.component').then((c) => c.IssuerComponent),
title: 'Issuer',
data: { hide: true, icon: 'assured_workload' },
},
{
path: 'marketplace',
loadComponent: () => import('./marketplace/marketplace.component').then((c) => c.MarketplaceComponent),
Expand Down
103 changes: 103 additions & 0 deletions app/src/app/apps/app/issuer/issuer.component.html
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>&nbsp; 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>&nbsp; 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
>&nbsp;
<!-- <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>&nbsp; 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>
7 changes: 7 additions & 0 deletions app/src/app/apps/app/issuer/issuer.component.scss
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;
}
22 changes: 22 additions & 0 deletions app/src/app/apps/app/issuer/issuer.component.spec.ts
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();
});
});
181 changes: 181 additions & 0 deletions app/src/app/apps/app/issuer/issuer.component.ts
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;
}
}
7 changes: 7 additions & 0 deletions app/src/app/apps/apps.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ export class AppsComponent {
imageUrl: '/icons/apps/text.jpg',
});

cards.push({
title: 'Issuer',
id: 'issuer',
description: 'Generic Credential Issuer app.',
imageUrl: '/icons/apps/issuer.webp',
});

cards.push({
title: 'Voluntaryist Covenant',
id: 'voluntaryist-covenant',
Expand Down
6 changes: 4 additions & 2 deletions app/src/app/registries/registry/freeid/freeid.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ <h3>Base Requirements</h3>
</p>

<p>
<strong>Sex:</strong> You must provide your sex in the form of birth sex. This is for health and security
reasons.
<strong>Gender:</strong> You must provide your gender in the form of birth gender. This is for health and
security reasons.
</p>

<p>
Expand All @@ -195,6 +195,8 @@ <h3>Base Requirements</h3>
</p>

<p><strong>Identifier:</strong> You must provide a decentralized identifier (DID) that you control.</p>

<p><strong>Expiration:</strong> FreeID Cards are valid for 5 years before they must be renewed.</p>
</mat-card-content>
</mat-card>

Expand Down

0 comments on commit ccdc0c9

Please sign in to comment.