diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index d5585cad..7cdbce40 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -43,7 +43,7 @@ jobs: # build the docker image for keycloak - name: Build Keycloak Docker Image - run: cd deploys/keycloak && docker-compose build keycloak && docker-compose push keycloak + run: cd deploys/keycloak && docker compose build keycloak && docker compose push keycloak # add the release, build the container and release it with the information for sentry - name: Build and push images diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f111f6f..eba7d0bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: run: npx playwright install --with-deps - name: Build keycloak - run: cd deploys/keycloak && docker-compose build keycloak + run: cd deploys/keycloak && docker compose build keycloak - name: Add entry to /etc/hosts run: echo "127.0.0.1 host.testcontainers.internal" | sudo tee -a /etc/hosts @@ -56,17 +56,26 @@ jobs: # token: ${{ secrets.CODECOV_TOKEN }} - name: Upload playwright results - if: always() + if: always() && steps.check_playwright_results.outputs.exists == 'true' uses: actions/upload-artifact@v4 with: name: playwright-results path: dist/.playwright retention-days: 30 + - name: Check if playwright results exist + id: check_playwright_results + run: echo "exists=$(if [ -d dist/.playwright ]; then echo true; else echo false; fi)" >> $GITHUB_ENV + + - name: Upload testcontainer logs - if: always() + if: always() && steps.check_testcontainer_logs.outputs.exists == 'true' uses: actions/upload-artifact@v4 with: name: testcontainer-logs path: tmp/logs retention-days: 30 + + - name: Check if testcontainer logs exist + id: check_testcontainer_logs + run: echo "exists=$(if [ -d tmp/logs ]; then echo true; else echo false; fi)" >> $GITHUB_ENV diff --git a/apps/holder-app-e2e/project.json b/apps/holder-app-e2e/project.json index 3e51f93a..a20991d9 100644 --- a/apps/holder-app-e2e/project.json +++ b/apps/holder-app-e2e/project.json @@ -4,7 +4,6 @@ "projectType": "application", "sourceRoot": "apps/holder-app-e2e/src", "implicitDependencies": ["holder-app"], - "// targets": "to see all targets run: nx show project holder-app-e2e --web", "targets": { "dev": { "command": "cd apps/holder-app-e2e && playwright test --ui" diff --git a/apps/holder-app/src/app/app.routes.ts b/apps/holder-app/src/app/app.routes.ts index 71e02634..4f8d5bdc 100644 --- a/apps/holder-app/src/app/app.routes.ts +++ b/apps/holder-app/src/app/app.routes.ts @@ -28,10 +28,12 @@ export const routes: Routes = [ { path: 'credentials', component: CredentialsListComponent, - }, - { - path: 'credentials/:id', - component: CredentialsShowComponent, + children: [ + { + path: ':id', + component: CredentialsShowComponent, + }, + ], }, { path: 'history', diff --git a/apps/holder-backend/src/app/credentials/credentials.module.ts b/apps/holder-backend/src/app/credentials/credentials.module.ts index 246942d4..61721738 100644 --- a/apps/holder-backend/src/app/credentials/credentials.module.ts +++ b/apps/holder-backend/src/app/credentials/credentials.module.ts @@ -3,9 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CredentialsController } from './credentials.controller'; import { CredentialsService } from './credentials.service'; import { Credential } from './entities/credential.entity'; +import { HttpModule } from '@nestjs/axios'; +import { CryptoModule, ResolverModule } from '@credhub/backend'; @Module({ - imports: [TypeOrmModule.forFeature([Credential])], + imports: [ + TypeOrmModule.forFeature([Credential]), + HttpModule, + ResolverModule, + CryptoModule, + ], controllers: [CredentialsController], providers: [CredentialsService], exports: [CredentialsService], diff --git a/apps/holder-backend/src/app/credentials/credentials.service.ts b/apps/holder-backend/src/app/credentials/credentials.service.ts index f016b552..ec3024d7 100644 --- a/apps/holder-backend/src/app/credentials/credentials.service.ts +++ b/apps/holder-backend/src/app/credentials/credentials.service.ts @@ -17,6 +17,12 @@ import { OnEvent } from '@nestjs/event-emitter'; import { USER_DELETED_EVENT, UserDeletedEvent } from '../auth/auth.service'; import { Interval } from '@nestjs/schedule'; import { createHash } from 'crypto'; +import { HttpService } from '@nestjs/axios'; +import { firstValueFrom } from 'rxjs'; +import { Verifier } from '@sd-jwt/types'; +import { JWK, JWTPayload } from '@sphereon/oid4vci-common'; +import { CryptoService, ResolverService } from '@credhub/backend'; +import { getListFromStatusListJWT } from '@sd-jwt/jwt-status-list'; type DateKey = 'exp' | 'nbf'; @Injectable() @@ -25,18 +31,57 @@ export class CredentialsService { constructor( @InjectRepository(Credential) - private credentialRepository: Repository + private credentialRepository: Repository, + private httpService: HttpService, + private resolverService: ResolverService, + private cryptoService: CryptoService ) { + const verifier: Verifier = async (data, signature) => { + const decodedVC = await this.instance.decode(`${data}.${signature}`); + const payload = decodedVC.jwt?.payload as JWTPayload; + const header = decodedVC.jwt?.header as JWK; + const publicKey = await this.resolverService.resolvePublicKey( + payload, + header + ); + //get the verifier based on the algorithm + const crypto = this.cryptoService.getCrypto(header.alg); + const verify = await crypto.getVerifier(publicKey); + return verify(data, signature).catch((err) => { + console.log(err); + return false; + }); + }; + + /** + * Fetch the status list from the uri. + * @param uri + * @returns + */ + const statusListFetcher: (uri: string) => Promise = async ( + uri: string + ) => { + const response = await firstValueFrom(this.httpService.get(uri)); + return response.data; + }; + this.instance = new SDJwtVcInstance({ hasher: digest, - verifier: () => Promise.resolve(true), + statusListFetcher, + statusValidator(status) { + if (status === 1) { + throw new Error('Status is not valid'); + } + return Promise.resolve(); + }, + verifier, }); } /** * Start an interval to update the status of the credentials. This is relevant for showing active credentials. */ - @Interval(1000 * 10) + @Interval(1000 * 3) updateStatusInterval() { this.updateStatus(); } @@ -134,11 +179,13 @@ export class CredentialsService { * Updates the state of a credential. This is relevant for showing active credentials. */ async updateStatus() { - //TODO: should we also set the state on expired? - //we are going for all credentials where credentials are not expired. It could happen that the status of a revoked credential will change. const credentials = await this.credentialRepository.find({ - where: { exp: MoreThanOrEqual(Date.now()) }, + where: { + //exp: MoreThanOrEqual(Date.now() / 1000), + //only a valid credential has an empty status. + status: IsNull(), + }, }); for (const credential of credentials) { await this.instance.verify(credential.value).then( @@ -150,6 +197,7 @@ export class CredentialsService { } }, async (err: Error) => { + console.log(err); if (err.message.includes('Status is not valid')) { //update the status in the db. credential.status = CredentialStatus.REVOKED; diff --git a/apps/holder-backend/src/app/oid4vc/oid4vp/oid4vp.service.ts b/apps/holder-backend/src/app/oid4vc/oid4vp/oid4vp.service.ts index f0732fef..17cefbb7 100644 --- a/apps/holder-backend/src/app/oid4vc/oid4vp/oid4vp.service.ts +++ b/apps/holder-backend/src/app/oid4vc/oid4vp/oid4vp.service.ts @@ -54,11 +54,11 @@ export class Oid4vpService { //parse the uri const parsedAuthReqURI = await op.parseAuthorizationRequestURI(data.url); - const verifiedAuthReqWithJWT: VerifiedAuthorizationRequest = - await op.verifyAuthorizationRequest( - parsedAuthReqURI.requestObjectJwt as string, - {} - ); + const verifiedAuthReqWithJWT: VerifiedAuthorizationRequest = await op + .verifyAuthorizationRequest(parsedAuthReqURI.requestObjectJwt, {}) + .catch(() => { + throw new ConflictException('Invalid request'); + }); const issuer = ( verifiedAuthReqWithJWT.authorizationRequestPayload @@ -71,6 +71,10 @@ export class Oid4vpService { const credentials = (await this.credentialsService.findAll(user)).map( (entry) => entry.value ); + + if (credentials.length === 0) { + throw new ConflictException('No matching credentials found'); + } //init the pex instance const pex = new PresentationExchange({ allVerifiableCredentials: credentials, @@ -82,16 +86,12 @@ export class Oid4vpService { await PresentationExchange.findValidPresentationDefinitions( verifiedAuthReqWithJWT.authorizationRequestPayload ); - // throws in error in case none was provided - if (pds.length === 0) { - throw new Error('No matching credentials found'); - } await this.sessionRepository.save( this.sessionRepository.create({ id: sessionId, // we need to store the JWT, because it serializes an object that can not be stored in the DB - requestObjectJwt: parsedAuthReqURI.requestObjectJwt as string, + requestObjectJwt: parsedAuthReqURI.requestObjectJwt, user, pds, }) @@ -100,11 +100,7 @@ export class Oid4vpService { // select the credentials for the presentation const result = await pex .selectVerifiableCredentialsForSubmission(pds[0].definition) - .catch((err: SelectResults) => { - console.log(err); - if (err.errors.length > 0) { - throw new ConflictException(err.errors); - } + .catch(() => { //instead of throwing an error, we return an empty array. This allows the user to show who sent the request for what. return { verifiableCredential: [] }; }); diff --git a/apps/issuer-backend-e2e/src/support/global-teardown.ts b/apps/issuer-backend-e2e/src/support/global-teardown.ts index 32ea345c..8f32cb65 100644 --- a/apps/issuer-backend-e2e/src/support/global-teardown.ts +++ b/apps/issuer-backend-e2e/src/support/global-teardown.ts @@ -3,5 +3,4 @@ module.exports = async function () { // Put clean up logic here (e.g. stopping services, docker-compose, etc.). // Hint: `globalThis` is shared between setup and teardown. - console.log(globalThis.__TEARDOWN_MESSAGE__); }; diff --git a/apps/issuer-backend/src/app/app.module.ts b/apps/issuer-backend/src/app/app.module.ts index 5763f60f..a666acca 100644 --- a/apps/issuer-backend/src/app/app.module.ts +++ b/apps/issuer-backend/src/app/app.module.ts @@ -4,16 +4,17 @@ import { ConfigModule } from '@nestjs/config'; import * as Joi from 'joi'; import { AuthModule, - CRYPTO_VALIDATION_SCHEMA, KEY_VALIDATION_SCHEMA, KeyModule, OIDC_VALIDATION_SCHEMA, + DB_VALIDATION_SCHEMA, + DbModule, } from '@credhub/relying-party-shared'; -import { DB_VALIDATION_SCHEMA, DbModule } from '@credhub/relying-party-shared'; import { CredentialsModule } from './credentials/credentials.module'; import { StatusModule } from './status/status.module'; import { ScheduleModule } from '@nestjs/schedule'; import { IssuerModule } from './issuer/issuer.module'; +import { CRYPTO_VALIDATION_SCHEMA } from '@credhub/backend'; @Module({ imports: [ diff --git a/apps/issuer-backend/src/app/credentials/credentials.controller.ts b/apps/issuer-backend/src/app/credentials/credentials.controller.ts index 8fb797af..8c482e83 100644 --- a/apps/issuer-backend/src/app/credentials/credentials.controller.ts +++ b/apps/issuer-backend/src/app/credentials/credentials.controller.ts @@ -10,11 +10,6 @@ import { AuthGuard } from 'nest-keycloak-connect'; export class CredentialsController { constructor(private readonly credentialsService: CredentialsService) {} - @Get() - findAll() { - return this.credentialsService.findAll(); - } - @Get(':id') findOne(@Param('id') id: string) { return this.credentialsService.findOne(id); diff --git a/apps/issuer-backend/src/app/credentials/credentials.service.ts b/apps/issuer-backend/src/app/credentials/credentials.service.ts index b9dad4a7..fac5b1fd 100644 --- a/apps/issuer-backend/src/app/credentials/credentials.service.ts +++ b/apps/issuer-backend/src/app/credentials/credentials.service.ts @@ -21,7 +21,11 @@ export class CredentialsService { } findOne(id: string) { - return this.credentialRepository.findOneOrFail({ where: { id } }); + return this.credentialRepository.findOneByOrFail({ id }); + } + + getBySessionId(sessionId: string) { + return this.credentialRepository.findBy({ sessionId }); } remove(id: string) { diff --git a/apps/issuer-backend/src/app/credentials/entities/credential.entity.ts b/apps/issuer-backend/src/app/credentials/entities/credential.entity.ts index 5692fbf6..92635f13 100644 --- a/apps/issuer-backend/src/app/credentials/entities/credential.entity.ts +++ b/apps/issuer-backend/src/app/credentials/entities/credential.entity.ts @@ -7,4 +7,7 @@ export class Credential { @Column() value: string; + + @Column() + sessionId: string; } diff --git a/apps/issuer-backend/src/app/issuer/dto/session-entry.dto.ts b/apps/issuer-backend/src/app/issuer/dto/session-entry.dto.ts new file mode 100644 index 00000000..f47696c7 --- /dev/null +++ b/apps/issuer-backend/src/app/issuer/dto/session-entry.dto.ts @@ -0,0 +1,8 @@ +import { Credential } from '../../credentials/entities/credential.entity'; +import { CredentialOfferSessionEntity } from '../entities/credential-offer-session.entity'; + +export class SessionEntryDto { + session: CredentialOfferSessionEntity; + + credentials: Credential[]; +} diff --git a/apps/issuer-backend/src/app/issuer/issuer.controller.ts b/apps/issuer-backend/src/app/issuer/issuer.controller.ts index 42f30a3f..e27c8da3 100644 --- a/apps/issuer-backend/src/app/issuer/issuer.controller.ts +++ b/apps/issuer-backend/src/app/issuer/issuer.controller.ts @@ -15,13 +15,18 @@ import { AuthGuard } from 'nest-keycloak-connect'; import { SessionResponseDto } from './dto/session-response.dto'; import { CredentialOfferSession } from './dto/credential-offer-session.dto'; import { DBStates } from '@credhub/relying-party-shared'; +import { CredentialsService } from '../credentials/credentials.service'; +import { SessionEntryDto } from './dto/session-entry.dto'; @UseGuards(AuthGuard) @ApiOAuth2([]) @ApiTags('sessions') @Controller('sessions') export class IssuerController { - constructor(private issuerService: IssuerService) {} + constructor( + private issuerService: IssuerService, + private credentialsService: CredentialsService + ) {} @ApiOperation({ summary: 'Lists all sessions' }) @Get() @@ -34,7 +39,7 @@ export class IssuerController { @ApiOperation({ summary: 'Returns the status for a session' }) @Get(':id') - async getSession(@Param('id') id: string): Promise { + async getSession(@Param('id') id: string): Promise { const session = (await this.issuerService.vcIssuer.credentialOfferSessions.get( id @@ -42,7 +47,11 @@ export class IssuerController { if (!session) { throw new NotFoundException(`Session with id ${id} not found`); } - return session; + const credentials = await this.credentialsService.getBySessionId(id); + return { + session, + credentials: credentials, + }; } @ApiOperation({ summary: 'Creates a new session request' }) diff --git a/apps/issuer-backend/src/app/issuer/issuer.service.ts b/apps/issuer-backend/src/app/issuer/issuer.service.ts index 1f5a8320..2cba96ca 100644 --- a/apps/issuer-backend/src/app/issuer/issuer.service.ts +++ b/apps/issuer-backend/src/app/issuer/issuer.service.ts @@ -30,12 +30,7 @@ import { import { IssuerDataService } from './issuer-data.service'; import { SessionRequestDto } from './dto/session-request.dto'; import { CredentialsService } from '../credentials/credentials.service'; -import { - CryptoImplementation, - CryptoService, - DBStates, - KeyService, -} from '@credhub/relying-party-shared'; +import { DBStates, KeyService } from '@credhub/relying-party-shared'; import { IssuerMetadata } from './types'; import { StatusService } from '../status/status.service'; import { SessionResponseDto } from './dto/session-response.dto'; @@ -45,7 +40,7 @@ import { Repository } from 'typeorm'; import { CNonceEntity } from './entities/c-nonce.entity'; import { URIStateEntity } from './entities/uri-state.entity'; import { CredentialOfferSessionEntity } from './entities/credential-offer-session.entity'; - +import { CryptoImplementation, CryptoService } from '@credhub/backend'; interface CredentialDataSupplierInput { credentialSubject: Record; exp: number; @@ -56,6 +51,8 @@ export class IssuerService { private express: ExpressSupport; vcIssuer: VcIssuer; + sessionMapper: Map = new Map(); + private crypto: CryptoImplementation; constructor( @@ -185,7 +182,7 @@ export class IssuerService { }; const credential: SdJwtDecodedVerifiableCredentialPayload = { - iat: new Date().getTime(), + iat: Math.round(new Date().getTime() / 1000), iss: args.credentialOffer.credential_offer.credential_issuer, vct: (args.credentialRequest as CredentialRequestSdJwtVc).vct, jti: v4(), @@ -193,8 +190,13 @@ export class IssuerService { .credentialSubject, //TODO: can be removed when correct type is set in PEX status: status as unknown as { idx: number; uri: string }, + //TODO: validate that the seconds and not milliseconds are used exp: args.credentialDataSupplierInput.exp, + nbf: args.credentialDataSupplierInput.nbf, }; + + // map the credential id with the session because we will be not able to get the session id in the sign callback. We are using the pre auth code for now. + this.sessionMapper.set(credential.jti as string, args.preAuthorizedCode); return Promise.resolve({ credential, format: 'vc+sd-jwt', @@ -253,12 +255,14 @@ export class IssuerService { ), { header: { kid: await this.keyService.getKid() } } ); + const sessionId = this.sessionMapper.get(args.credential.jti as string); + this.sessionMapper.delete(args.credential.jti as string); await this.credentialsService.create({ value: jwt, id: args.credential.jti as string, + sessionId, }); return jwt; - // return decodeSdJwtVc(jwt, digest) as unknown as Promise; }; //create the issuer instance diff --git a/apps/issuer-backend/src/app/status/status.controller.ts b/apps/issuer-backend/src/app/status/status.controller.ts index 6666f447..f664bb16 100644 --- a/apps/issuer-backend/src/app/status/status.controller.ts +++ b/apps/issuer-backend/src/app/status/status.controller.ts @@ -81,7 +81,7 @@ export class StatusController { @Public() @ApiOperation({ summary: 'Get the status of a specific index' }) @Get(':id/:index') - getStatus(@Param('id') id: string, @Param('index') index: string) { + getStatus(@Param('id') id: string, @Param('index') index: number) { return this.statusService.getStatus(id, index).then((status) => ({ status, })); diff --git a/apps/issuer-backend/src/app/status/status.service.ts b/apps/issuer-backend/src/app/status/status.service.ts index b9ee9f2f..f3dec3e7 100644 --- a/apps/issuer-backend/src/app/status/status.service.ts +++ b/apps/issuer-backend/src/app/status/status.service.ts @@ -163,14 +163,14 @@ export class StatusService { * @param index * @returns */ - getStatus(id: string, index: string) { + getStatus(id: string, index: number) { return this.statusRepository.findOneBy({ id }).then((list) => { if (!list) { throw new NotFoundException(); } const decodedList = this.decodeList(list.list); const statusList = new List(decodedList, list.bitsPerStatus); - return statusList.getStatus(parseInt(index)); + return statusList.getStatus(index); }); } @@ -196,6 +196,7 @@ export class StatusService { listEntry.jwt = jwt; listEntry.exp = exp; await this.statusRepository.save(listEntry); + console.log('Status set'); } /** diff --git a/apps/issuer-frontend/src/app/app.config.ts b/apps/issuer-frontend/src/app/app.config.ts index 4ac86784..3dd62d45 100644 --- a/apps/issuer-frontend/src/app/app.config.ts +++ b/apps/issuer-frontend/src/app/app.config.ts @@ -12,6 +12,7 @@ import { } from '@credhub/issuer-shared'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; export const appConfig: ApplicationConfig = { providers: [ @@ -28,6 +29,7 @@ export const appConfig: ApplicationConfig = { }, provideAnimationsAsync(), importProvidersFrom(ApiModule), + { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 } }, { provide: Configuration, deps: [IssuerConfigService], diff --git a/apps/issuer-frontend/src/app/sessions/sessions-list/sessions-list.component.ts b/apps/issuer-frontend/src/app/sessions/sessions-list/sessions-list.component.ts index 7127a245..a4858470 100644 --- a/apps/issuer-frontend/src/app/sessions/sessions-list/sessions-list.component.ts +++ b/apps/issuer-frontend/src/app/sessions/sessions-list/sessions-list.component.ts @@ -43,7 +43,6 @@ export class SessionsListComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { await this.loadSessions(); - //this.interval = setInterval(() => this.loadSessions(), 1000); } private loadSessions() { @@ -66,13 +65,18 @@ export class SessionsListComponent implements OnInit, OnDestroy { : this.dataSource.data.forEach((row) => this.selection.select(row)); } - deleteSelected() { + async deleteSelected() { if (!confirm('Are you sure you want to delete these sessions?')) return; for (const session of this.selection.selected) { - firstValueFrom( + await firstValueFrom( this.sessionsApiService.issuerControllerDelete(session.id) ); + //remove the element from the data source + const index = this.dataSource.data.indexOf(session); + if (index > -1) { + this.dataSource.data.splice(index, 1); + this.dataSource.data = [...this.dataSource.data]; + } } - this.loadSessions(); } } diff --git a/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.html b/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.html index 0c27adf6..9e2cf7c5 100644 --- a/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.html +++ b/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.html @@ -1,3 +1,70 @@ -Back - -
{{ session | json }}
+
+ + + + + + + + + + + schedule +

+ {{ session.session.createdAt | date : 'medium' }} +

+
+ + sync +

+ {{ session.session.status }} +

+
+
+ + + delete +

Delete

+
+
+
+
+
+

Credentials

+ + @for(entry of credentials; track credentials) { + + + + {{ entry.credential.jwt.payload.vct }} + + + {{ entry.id }} + + +

Status: {{ entry.status }}

+ + @for(claim of entry.claims; track claim) { + +
{{ claim.key }}
+
{{ claim.value }}
+
+ } +
+ + + +
+ } +
+
+
diff --git a/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.ts b/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.ts index 92ca7452..a07b4483 100644 --- a/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.ts +++ b/apps/issuer-frontend/src/app/sessions/sessions-show/sessions-show.component.ts @@ -4,28 +4,56 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { firstValueFrom } from 'rxjs'; import { MatButtonModule } from '@angular/material/button'; import { - CredentialOfferSession, + SessionEntryDto, SessionsApiService, + StatusApiService, } from '@credhub/issuer-shared'; +import { SDJwtVcInstance } from '@sd-jwt/sd-jwt-vc'; +import { digest } from '@sd-jwt/crypto-browser'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatListModule } from '@angular/material/list'; +import { MatCardModule } from '@angular/material/card'; +import { MatIconModule } from '@angular/material/icon'; +import { FlexLayoutModule } from 'ng-flex-layout'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; @Component({ selector: 'app-sessions-show', standalone: true, - imports: [CommonModule, MatButtonModule, RouterModule], + imports: [ + CommonModule, + MatButtonModule, + RouterModule, + MatExpansionModule, + MatListModule, + MatCardModule, + MatIconModule, + FlexLayoutModule, + MatSnackBarModule, + ], templateUrl: './sessions-show.component.html', styleUrl: './sessions-show.component.scss', }) export class SessionsShowComponent implements OnInit, OnDestroy { - session!: CredentialOfferSession; + session!: SessionEntryDto; interval!: ReturnType; id!: string; sessionId!: string; + private sdjwt: SDJwtVcInstance; + credentials: any[] = []; constructor( private sessionsApiService: SessionsApiService, private route: ActivatedRoute, - private router: Router - ) {} + private router: Router, + private statusApiService: StatusApiService, + private snackbar: MatSnackBar + ) { + this.sdjwt = new SDJwtVcInstance({ + hasher: digest, + hashAlg: 'SHA-256', + }); + } ngOnDestroy(): void { clearInterval(this.interval); } @@ -34,13 +62,40 @@ export class SessionsShowComponent implements OnInit, OnDestroy { this.id = this.route.snapshot.paramMap.get('id') as string; this.sessionId = this.route.snapshot.paramMap.get('sessionId') as string; await this.loadSessions(); - this.interval = setInterval(() => this.loadSessions(), 1000); + //this.interval = setInterval(() => this.loadSessions(), 1000); } - private loadSessions() { - firstValueFrom( + private async loadSessions() { + await firstValueFrom( this.sessionsApiService.issuerControllerGetSession(this.sessionId) ).then((session) => (this.session = session)); + + this.credentials = await Promise.all( + this.session.credentials.map(async (credential) => { + const sdJWT = await this.sdjwt.decode(credential.value); + return { + id: credential.id, + credential: sdJWT, + claims: this.getClaims( + (await this.sdjwt.getClaims(credential.value)) as Record< + string, + unknown + > + ), + status: await this.getStatus( + (sdJWT.jwt!.payload!['status']! as any).status_list + ), + }; + }) + ); + } + + getClaims(credential: Record) { + //remove all values from the record that are in the excludedFields array + const excludedFields = ['iat', 'iss', 'vct', 'jti', 'status', 'exp', 'cnf']; + return Object.keys(credential) + .filter((key) => !excludedFields.includes(key)) + .map((key) => ({ key, value: credential[key] })); } delete() { @@ -49,4 +104,20 @@ export class SessionsShowComponent implements OnInit, OnDestroy { this.sessionsApiService.issuerControllerDelete(this.sessionId) ).then(() => this.router.navigate(['/templates', this.id])); } + + revoke(status: { idx: number; uri: string }) { + const listId = status.uri.split('/').pop() as string; + firstValueFrom( + this.statusApiService.statusControllerChangeStatus(listId, status.idx, { + status: 1, + }) + ).then(() => this.snackbar.open('Credential revoked')); + } + + getStatus(status: { idx: number; uri: string }) { + const listId = status.uri.split('/').pop() as string; + return firstValueFrom( + this.statusApiService.statusControllerGetStatus(listId, status.idx) + ).then((status) => (status.status === 0 ? 'active' : 'revoked')); + } } diff --git a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html index 9688da35..de2e58fa 100644 --- a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html +++ b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.html @@ -72,5 +72,8 @@

Status: {{ issuerService.statusEvent.value }}

+ Go to session entry diff --git a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts index 4e72eb36..f18f9edd 100644 --- a/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts +++ b/apps/issuer-frontend/src/app/templates/templates-issue/templates-issue.component.ts @@ -44,6 +44,7 @@ export class TemplatesIssueComponent implements OnInit, OnDestroy { pinField = new FormControl(''); id!: string; template!: Template; + sessionId?: string; constructor( public issuerService: IssuerService, @@ -77,6 +78,9 @@ export class TemplatesIssueComponent implements OnInit, OnDestroy { const response = await this.issuerService.getUrl(this.id, this.form.value, { pin: this.pinRequired.value as boolean, }); + + this.sessionId = response.id; + this.qrCodeField.setValue(response.uri); if (response.userPin) { this.pinField.setValue(response.userPin); @@ -87,6 +91,6 @@ export class TemplatesIssueComponent implements OnInit, OnDestroy { copyValue(value: string) { navigator.clipboard.writeText(value); - this.snackBar.open('Copied to clipboard', 'Close', { duration: 2000 }); + this.snackBar.open('Copied to clipboard'); } } diff --git a/apps/verifier-backend-e2e/src/support/global-setup.ts b/apps/verifier-backend-e2e/src/support/global-setup.ts index c1f51444..f1b12435 100644 --- a/apps/verifier-backend-e2e/src/support/global-setup.ts +++ b/apps/verifier-backend-e2e/src/support/global-setup.ts @@ -3,8 +3,5 @@ var __TEARDOWN_MESSAGE__: string; module.exports = async function () { // Start services that that the app needs to run (e.g. database, docker-compose, etc.). - console.log('\nSetting up...\n'); - // Hint: Use `globalThis` to pass variables to global teardown. - globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n'; }; diff --git a/apps/verifier-backend-e2e/src/support/global-teardown.ts b/apps/verifier-backend-e2e/src/support/global-teardown.ts index 32ea345c..8f32cb65 100644 --- a/apps/verifier-backend-e2e/src/support/global-teardown.ts +++ b/apps/verifier-backend-e2e/src/support/global-teardown.ts @@ -3,5 +3,4 @@ module.exports = async function () { // Put clean up logic here (e.g. stopping services, docker-compose, etc.). // Hint: `globalThis` is shared between setup and teardown. - console.log(globalThis.__TEARDOWN_MESSAGE__); }; diff --git a/apps/verifier-backend/src/app/app.module.ts b/apps/verifier-backend/src/app/app.module.ts index 8c51ca05..05db13fa 100644 --- a/apps/verifier-backend/src/app/app.module.ts +++ b/apps/verifier-backend/src/app/app.module.ts @@ -5,13 +5,13 @@ import * as Joi from 'joi'; import { VerifierModule } from './verifier/verifier.module'; import { AuthModule, - CRYPTO_VALIDATION_SCHEMA, DB_VALIDATION_SCHEMA, DbModule, KeyModule, OIDC_VALIDATION_SCHEMA, } from '@credhub/relying-party-shared'; import { TemplatesModule } from './templates/templates.module'; +import { CRYPTO_VALIDATION_SCHEMA } from '@credhub/backend'; @Module({ imports: [ diff --git a/apps/verifier-backend/src/app/verifier/relying-party-manager.service.ts b/apps/verifier-backend/src/app/verifier/relying-party-manager.service.ts index 72ff117f..fd3050d0 100644 --- a/apps/verifier-backend/src/app/verifier/relying-party-manager.service.ts +++ b/apps/verifier-backend/src/app/verifier/relying-party-manager.service.ts @@ -31,15 +31,15 @@ import { importJWK, jwtVerify, KeyLike } from 'jose'; import { DBRPSessionManager } from './session-manager'; import { EventEmitter } from 'node:events'; import { ConfigService } from '@nestjs/config'; -import { KeyService, CryptoService } from '@credhub/relying-party-shared'; -import { ResolverService } from '../resolver/resolver.service'; +import { KeyService } from '@credhub/relying-party-shared'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom } from 'rxjs'; import { TemplatesService } from '../templates/templates.service'; import { Template } from '../templates/dto/template.dto'; import { InjectRepository } from '@nestjs/typeorm'; -import { In, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { AuthStateEntity } from './entity/auth-state.entity'; +import { CryptoService, ResolverService } from '@credhub/backend'; @Injectable() export class RelyingPartyManagerService { @@ -52,7 +52,7 @@ export class RelyingPartyManagerService { constructor( private resolverService: ResolverService, private configService: ConfigService, - private httpSerivce: HttpService, + private httpService: HttpService, private cryptoService: CryptoService, private templateService: TemplatesService, @InjectRepository(AuthStateEntity) @@ -248,7 +248,7 @@ export class RelyingPartyManagerService { header ); //get the verifier based on the algorithm - const crypto = this.cryptoService.getCrypto(header.alg as string); + const crypto = this.cryptoService.getCrypto(header.alg); const verify = await crypto.getVerifier(publicKey); return verify(data, signature).catch((err) => { console.log(err); @@ -282,7 +282,7 @@ export class RelyingPartyManagerService { const statusListFetcher: (uri: string) => Promise = async ( uri: string ) => { - const response = await firstValueFrom(this.httpSerivce.get(uri)); + const response = await firstValueFrom(this.httpService.get(uri)); return response.data; }; diff --git a/apps/verifier-backend/src/app/verifier/session-manager.ts b/apps/verifier-backend/src/app/verifier/session-manager.ts index f0452e45..4014eaca 100644 --- a/apps/verifier-backend/src/app/verifier/session-manager.ts +++ b/apps/verifier-backend/src/app/verifier/session-manager.ts @@ -203,7 +203,6 @@ export class DBRPSessionManager implements IRPSessionManager { lastUpdated: event.timestamp, payload: event.subject.payload, }); - console.log(element); await this.authStateRepository.save(element); } diff --git a/apps/verifier-backend/src/app/verifier/verifier.module.ts b/apps/verifier-backend/src/app/verifier/verifier.module.ts index 3c13b6ee..d8a9edbe 100644 --- a/apps/verifier-backend/src/app/verifier/verifier.module.ts +++ b/apps/verifier-backend/src/app/verifier/verifier.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { RelyingPartyManagerService } from './relying-party-manager.service'; import { SiopController } from './siop.controller'; -import { ResolverModule } from '../resolver/resolver.module'; import { HttpModule } from '@nestjs/axios'; import { TemplatesModule } from '../templates/templates.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuthStateEntity } from './entity/auth-state.entity'; +import { ResolverModule } from '@credhub/backend'; @Module({ imports: [ diff --git a/deploys/issuer/config/issuer-backend/metadata.json b/deploys/issuer/config/issuer-backend/metadata.json index bee85b38..eb1d0ab8 100644 --- a/deploys/issuer/config/issuer-backend/metadata.json +++ b/deploys/issuer/config/issuer-backend/metadata.json @@ -4,7 +4,7 @@ "name": "German Government", "locale": "en-US", "logo": { - "url": "https://www.bmi.bund.de/SharedDocs/bilder/DE/schmuckbilder/moderne-verwaltung/paesse-ausweise/personalausweis_vorderseite_ab_august_2021.jpg?__blob=poster&v=2" + "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/Flagge_Deutschland.jpg/640px-Flagge_Deutschland.jpg" } } ] diff --git a/libs/backend/.eslintrc.json b/libs/backend/.eslintrc.json new file mode 100644 index 00000000..9d9c0db5 --- /dev/null +++ b/libs/backend/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/backend/README.md b/libs/backend/README.md new file mode 100644 index 00000000..eca02527 --- /dev/null +++ b/libs/backend/README.md @@ -0,0 +1,7 @@ +# backend + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test backend` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/backend/jest.config.ts b/libs/backend/jest.config.ts new file mode 100644 index 00000000..d6df4896 --- /dev/null +++ b/libs/backend/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'backend', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/libs/backend', +}; diff --git a/libs/backend/project.json b/libs/backend/project.json new file mode 100644 index 00000000..ff0c52b6 --- /dev/null +++ b/libs/backend/project.json @@ -0,0 +1,9 @@ +{ + "name": "backend", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/backend/src", + "projectType": "library", + "tags": [], + "// targets": "to see all targets run: nx show project backend --web", + "targets": {} +} diff --git a/libs/backend/src/index.ts b/libs/backend/src/index.ts new file mode 100644 index 00000000..c4b01a21 --- /dev/null +++ b/libs/backend/src/index.ts @@ -0,0 +1,5 @@ +export * from './lib/resolver/resolver.service'; +export * from './lib/resolver/resolver.module'; +export * from './lib/crypto/crypto.service'; +export * from './lib/crypto/crypto-implementation'; +export * from './lib/crypto/crypto.module'; diff --git a/libs/relying-party-shared/src/lib/crypto/crypto-implementation.ts b/libs/backend/src/lib/crypto/crypto-implementation.ts similarity index 100% rename from libs/relying-party-shared/src/lib/crypto/crypto-implementation.ts rename to libs/backend/src/lib/crypto/crypto-implementation.ts diff --git a/libs/relying-party-shared/src/lib/crypto/crypto.module.ts b/libs/backend/src/lib/crypto/crypto.module.ts similarity index 100% rename from libs/relying-party-shared/src/lib/crypto/crypto.module.ts rename to libs/backend/src/lib/crypto/crypto.module.ts diff --git a/libs/relying-party-shared/src/lib/crypto/crypto.service.ts b/libs/backend/src/lib/crypto/crypto.service.ts similarity index 87% rename from libs/relying-party-shared/src/lib/crypto/crypto.service.ts rename to libs/backend/src/lib/crypto/crypto.service.ts index 2d3e4712..109151c3 100644 --- a/libs/relying-party-shared/src/lib/crypto/crypto.service.ts +++ b/libs/backend/src/lib/crypto/crypto.service.ts @@ -14,7 +14,9 @@ export class CryptoService { return this.configServie.get('CRYPTO_ALG') as CryptoType; } - getCrypto(alg = this.configServie.get('CRYPTO_ALG')): CryptoImplementation { + getCrypto( + alg = this.configServie.get('CRYPTO_ALG') + ): CryptoImplementation { switch (alg) { case 'Ed25519': return ED25519; diff --git a/libs/relying-party-shared/src/lib/crypto/ed25519.ts b/libs/backend/src/lib/crypto/ed25519.ts similarity index 100% rename from libs/relying-party-shared/src/lib/crypto/ed25519.ts rename to libs/backend/src/lib/crypto/ed25519.ts diff --git a/apps/verifier-backend/src/app/resolver/resolver.module.ts b/libs/backend/src/lib/resolver/resolver.module.ts similarity index 100% rename from apps/verifier-backend/src/app/resolver/resolver.module.ts rename to libs/backend/src/lib/resolver/resolver.module.ts diff --git a/apps/verifier-backend/src/app/resolver/resolver.service.ts b/libs/backend/src/lib/resolver/resolver.service.ts similarity index 82% rename from apps/verifier-backend/src/app/resolver/resolver.service.ts rename to libs/backend/src/lib/resolver/resolver.service.ts index 9ee559b7..c7f46803 100644 --- a/apps/verifier-backend/src/app/resolver/resolver.service.ts +++ b/libs/backend/src/lib/resolver/resolver.service.ts @@ -28,23 +28,30 @@ export class ResolverService { * @returns */ async resolvePublicKey(payload: JWTPayload, header: JWK): Promise { + if (!payload.iss) { + throw new Error('Issuer not found'); + } + if (header.x5c) { - const cert = new X509Certificate(Buffer.from(header.x5c[0], 'base64')); - //TODO: implement the validation of the certificate chain and also the comparison of the identifier - if (cert.subject !== payload.iss) { + //TODO: validate the certificate and the chain of trust! + const certs = header.x5c.map( + (cert) => new X509Certificate(Buffer.from(cert, 'base64')) + ); + const cert = certs[0]; + if (!cert.subjectAltName?.includes(payload.iss.split('://')[1])) { throw new Error('Subject and issuer do not match'); } return cert.publicKey.export({ format: 'jwk' }) as JWK; } //checl if the key is in the header as jwk - if (header.jwk) { - return header.jwk as JWK; + if (header['jwk']) { + return header['jwk'] as JWK; } //check if the issuer is a did if (payload.iss.startsWith('did:')) { const did = await resolver.resolve(payload.iss); - if (!did) { + if (!did.didDocument?.verificationMethod) { throw new ConflictException('DID not found'); } //TODO: header.kid can be relative or absolute, we need to handle this diff --git a/libs/backend/src/lib/test.spec.ts b/libs/backend/src/lib/test.spec.ts new file mode 100644 index 00000000..b607c4a8 --- /dev/null +++ b/libs/backend/src/lib/test.spec.ts @@ -0,0 +1,5 @@ +describe('Testspec', () => { + it('true', () => { + expect(true).toBeTruthy(); + }); +}); diff --git a/libs/backend/tsconfig.json b/libs/backend/tsconfig.json new file mode 100644 index 00000000..f5b85657 --- /dev/null +++ b/libs/backend/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/backend/tsconfig.lib.json b/libs/backend/tsconfig.lib.json new file mode 100644 index 00000000..c297a248 --- /dev/null +++ b/libs/backend/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/backend/tsconfig.spec.json b/libs/backend/tsconfig.spec.json new file mode 100644 index 00000000..9b2a121d --- /dev/null +++ b/libs/backend/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.html b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.html index 7521cf7a..ea663fdc 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.html +++ b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.html @@ -1,77 +1,56 @@ -
-

Credentials

-

Archived credentials

- - - - - - - -
- - Search - - search - -
- @for (credential of credentials; track credential) { - -
+
+
+
+

Credentials

+

Archived credentials

+ + + + + +
+ + Search + + search + +
+ + @for (credential of credentials; track credential) { + + Credential +
{{ credential.display.name }}
+
{{ credential.issuer.name }}
+
+ } +
+
-
-

{{ credential.display.name }}

- {{ credential.issuer.name }} + *ngIf="credentials.length === 0" + fxLayout="column" + fxLayoutAlign="center center" + > +

No credentials found

- } -
-
-

No credentials found

+
+ +
diff --git a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.scss b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.scss index 3fa77f92..03785acc 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.scss +++ b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.scss @@ -3,35 +3,6 @@ display: block; } -.mat-mdc-list-item-icon { - margin: 0; -} - -.image { - margin: 16px 0; - height: 200px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); - border-radius: 10px; -} - -.card { - border-radius: 10px; - background-color: #D7E3FF; - padding: 10px; - - .icon { - width: 50px; - height: 50px; - border-radius: 5px; - background-size: contain; - background-repeat: no-repeat; - background-position: center center; - } - h4 { - margin: 0; - } -} - #search { width: 100%; } diff --git a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.ts b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.ts index 0c6abdbc..868b83a8 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.ts +++ b/libs/holder-shared/src/lib/credentials/credentials-list/credentials-list.component.ts @@ -1,18 +1,26 @@ import { Component, OnInit } from '@angular/core'; import { CredentialResponse, CredentialsApiService } from '../../api/'; -import { firstValueFrom } from 'rxjs'; +import { filter, firstValueFrom } from 'rxjs'; import { MatListModule } from '@angular/material/list'; import { CredentialsShowComponent } from '../credentials-show/credentials-show.component'; import { MatCardModule } from '@angular/material/card'; import { CommonModule } from '@angular/common'; import { CredentialsSupportedDisplay } from '@sphereon/oid4vci-common'; -import { RouterLink } from '@angular/router'; +import { + ActivatedRoute, + NavigationEnd, + Router, + RouterLink, + RouterModule, +} from '@angular/router'; import { FlexLayoutModule } from 'ng-flex-layout'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatButtonModule } from '@angular/material/button'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; +import { BreakpointObserver } from '@angular/cdk/layout'; +import { CredentialsService } from '../credentials.service'; export interface CredentialList extends CredentialResponse { display: CredentialsSupportedDisplay; @@ -35,6 +43,7 @@ type ShowType = 'all' | 'archived'; MatButtonModule, ReactiveFormsModule, MatInputModule, + RouterModule, ], templateUrl: './credentials-list.component.html', styleUrl: './credentials-list.component.scss', @@ -44,14 +53,36 @@ export class CredentialsListComponent implements OnInit { search: FormControl = new FormControl(''); - render: 'image' | 'card' = 'card'; - type: ShowType = 'all'; - constructor(private credentialsApiService: CredentialsApiService) {} + credentialShown = false; + mobile = false; + + constructor( + private credentialsApiService: CredentialsApiService, + private route: ActivatedRoute, + private router: Router, + breakpointObserver: BreakpointObserver, + private credentialsService: CredentialsService + ) { + breakpointObserver + .observe('(max-width: 599px)') + .subscribe((result) => (this.mobile = result.matches)); + } async ngOnInit(): Promise { this.loadCredentials(); + this.credentialShown = this.route.firstChild !== null; + this.router.events + .pipe(filter((event) => event instanceof NavigationEnd)) + .subscribe(() => (this.credentialShown = this.route.firstChild !== null)); + + this.credentialsService.deletedEmitter.subscribe( + (credentialId) => + (this.credentials = this.credentials.filter( + (credential) => credential.id !== credentialId + )) + ); } /** diff --git a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.html b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.html index e24c40af..38dba611 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.html +++ b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.html @@ -1,60 +1,72 @@ -
-
-
- -

{{ metadata.display![0].name }}

+
+
+ Credential image +
+
+ + + + + +
+
+
+ + {{ credential.issuer.name }} +
+

{{ metadata.display![0].name }}

+
- - - - -
- - - {{ credential.issuer.name }} - {{ - status - }} + Claims @for (claim of claims; track claim) { - {{ claim.key }} - {{ claim.value }} + {{ claim.value }} + {{ claim.key }} } - - - Issuance Date - {{ getClaim('iat') | date : 'medium' }} - - - Expiration Date - {{ getClaim('exp') | date : 'medium' }} - - + + + {{ + getClaimAsNumber('iat') * 1000 | date : 'medium' + }} + Issuance Date + + + {{ + getClaimAsNumber('exp') * 1000 | date : 'medium' + }} + Expiration Date + +
diff --git a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.scss b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.scss index 14d5c3c8..a31f4cbd 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.scss +++ b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.scss @@ -1,20 +1,20 @@ :host { padding: 16px; display: block; - max-width: 400px; -} - -#credential { - margin: auto; + width: 100%; } .image { - width: 100%; + max-width: 400px; height: 200px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); border-radius: 10px; } -.error-state { - color: red; +.issuer-image { + width: 100px; +} + +.header { + width: 100%; } diff --git a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.ts b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.ts index 0158f76d..56359f72 100644 --- a/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.ts +++ b/libs/holder-shared/src/lib/credentials/credentials-show/credentials-show.component.ts @@ -11,6 +11,7 @@ import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { CredentialsService } from '../credentials.service'; @Component({ selector: 'lib-credentials-show', @@ -39,7 +40,8 @@ export class CredentialsShowComponent implements OnInit { private credentialsApiService: CredentialsApiService, private router: Router, private route: ActivatedRoute, - private snackBar: MatSnackBar + private snackBar: MatSnackBar, + private credentialsService: CredentialsService ) {} async ngOnInit(): Promise { @@ -68,9 +70,9 @@ export class CredentialsShowComponent implements OnInit { ] as number; if (this.credential.status === CredentialResponse.StatusEnum.revoked) { this.status = 'revoked'; - } else if (expired && new Date(expired) < new Date()) { + } else if (expired && new Date(expired * 1000) < new Date()) { this.status = 'expired'; - } else if (created && new Date(created) > new Date()) { + } else if (created && new Date(created * 1000) > new Date()) { this.status = 'not valid yet'; } } @@ -105,6 +107,10 @@ export class CredentialsShowComponent implements OnInit { ] as string; } + getClaimAsNumber(key: string): number { + return this.getClaim(key) as unknown as number; + } + copyRaw() { navigator.clipboard.writeText(this.credential.value); this.snackBar.open('Credential copied to clipboard', 'Close', { @@ -117,6 +123,7 @@ export class CredentialsShowComponent implements OnInit { await firstValueFrom( this.credentialsApiService.credentialsControllerRemove(this.credential.id) ); + this.credentialsService.deletedEmitter.emit(this.credential.id); this.router.navigate(['/credentials']); } } diff --git a/libs/holder-shared/src/lib/credentials/credentials.service.spec.ts b/libs/holder-shared/src/lib/credentials/credentials.service.spec.ts new file mode 100644 index 00000000..7d8ae408 --- /dev/null +++ b/libs/holder-shared/src/lib/credentials/credentials.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CredentialsService } from './credentials.service'; + +describe('CredentialsService', () => { + let service: CredentialsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CredentialsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/libs/holder-shared/src/lib/credentials/credentials.service.ts b/libs/holder-shared/src/lib/credentials/credentials.service.ts new file mode 100644 index 00000000..feeb4b5c --- /dev/null +++ b/libs/holder-shared/src/lib/credentials/credentials.service.ts @@ -0,0 +1,8 @@ +import { EventEmitter, Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class CredentialsService { + public deletedEmitter = new EventEmitter(); +} diff --git a/libs/issuer-shared/src/lib/api/.openapi-generator/FILES b/libs/issuer-shared/src/lib/api/.openapi-generator/FILES index 57d2cb11..0b7e4a33 100644 --- a/libs/issuer-shared/src/lib/api/.openapi-generator/FILES +++ b/libs/issuer-shared/src/lib/api/.openapi-generator/FILES @@ -17,11 +17,13 @@ model/createListDto.ts model/credential.ts model/credentialConfigurationSupportedV1013.ts model/credentialOfferSession.ts +model/credentialOfferSessionEntity.ts model/credentialsSupportedDisplay.ts model/imageInfo.ts model/metadata.ts model/metadataDisplay.ts model/models.ts +model/sessionEntryDto.ts model/sessionRequestDto.ts model/sessionResponseDto.ts model/statusList.ts diff --git a/libs/issuer-shared/src/lib/api/api/credentials.service.ts b/libs/issuer-shared/src/lib/api/api/credentials.service.ts index 9fddba62..cbcbb880 100644 --- a/libs/issuer-shared/src/lib/api/api/credentials.service.ts +++ b/libs/issuer-shared/src/lib/api/api/credentials.service.ts @@ -92,72 +92,6 @@ export class CredentialsApiService { return httpParams; } - /** - * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param reportProgress flag to report request and response progress. - */ - public credentialsControllerFindAll(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public credentialsControllerFindAll(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; - public credentialsControllerFindAll(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; - public credentialsControllerFindAll(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { - - let localVarHeaders = this.defaultHeaders; - - let localVarCredential: string | undefined; - // authentication (oauth2) required - localVarCredential = this.configuration.lookupCredential('oauth2'); - if (localVarCredential) { - localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); - } - - let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; - if (localVarHttpHeaderAcceptSelected === undefined) { - // to determine the Accept header - const httpHeaderAccepts: string[] = [ - 'application/json' - ]; - localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); - } - if (localVarHttpHeaderAcceptSelected !== undefined) { - localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); - } - - let localVarHttpContext: HttpContext | undefined = options && options.context; - if (localVarHttpContext === undefined) { - localVarHttpContext = new HttpContext(); - } - - let localVarTransferCache: boolean | undefined = options && options.transferCache; - if (localVarTransferCache === undefined) { - localVarTransferCache = true; - } - - - let responseType_: 'text' | 'json' | 'blob' = 'json'; - if (localVarHttpHeaderAcceptSelected) { - if (localVarHttpHeaderAcceptSelected.startsWith('text')) { - responseType_ = 'text'; - } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { - responseType_ = 'json'; - } else { - responseType_ = 'blob'; - } - } - - let localVarPath = `/credentials`; - return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, - { - context: localVarHttpContext, - responseType: responseType_, - withCredentials: this.configuration.withCredentials, - headers: localVarHeaders, - observe: observe, - transferCache: localVarTransferCache, - reportProgress: reportProgress - } - ); - } - /** * @param id * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. diff --git a/libs/issuer-shared/src/lib/api/api/sessions.service.ts b/libs/issuer-shared/src/lib/api/api/sessions.service.ts index 2fb9c71f..20d2859e 100644 --- a/libs/issuer-shared/src/lib/api/api/sessions.service.ts +++ b/libs/issuer-shared/src/lib/api/api/sessions.service.ts @@ -21,6 +21,8 @@ import { Observable } from 'rxjs'; // @ts-ignore import { CredentialOfferSession } from '../model/credentialOfferSession'; // @ts-ignore +import { SessionEntryDto } from '../model/sessionEntryDto'; +// @ts-ignore import { SessionRequestDto } from '../model/sessionRequestDto'; // @ts-ignore import { SessionResponseDto } from '../model/sessionResponseDto'; @@ -173,9 +175,9 @@ export class SessionsApiService { * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public issuerControllerGetSession(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; - public issuerControllerGetSession(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public issuerControllerGetSession(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public issuerControllerGetSession(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public issuerControllerGetSession(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public issuerControllerGetSession(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; public issuerControllerGetSession(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { if (id === null || id === undefined) { throw new Error('Required parameter id was null or undefined when calling issuerControllerGetSession.'); @@ -225,7 +227,7 @@ export class SessionsApiService { } let localVarPath = `/sessions/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; - return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, responseType: responseType_, diff --git a/libs/issuer-shared/src/lib/api/api/status.service.ts b/libs/issuer-shared/src/lib/api/api/status.service.ts index c3a2ca0a..cc84cea7 100644 --- a/libs/issuer-shared/src/lib/api/api/status.service.ts +++ b/libs/issuer-shared/src/lib/api/api/status.service.ts @@ -419,10 +419,10 @@ export class StatusApiService { * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. */ - public statusControllerGetStatus(id: string, index: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; - public statusControllerGetStatus(id: string, index: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; - public statusControllerGetStatus(id: string, index: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; - public statusControllerGetStatus(id: string, index: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + public statusControllerGetStatus(id: string, index: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public statusControllerGetStatus(id: string, index: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public statusControllerGetStatus(id: string, index: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public statusControllerGetStatus(id: string, index: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { if (id === null || id === undefined) { throw new Error('Required parameter id was null or undefined when calling statusControllerGetStatus.'); } @@ -472,7 +472,7 @@ export class StatusApiService { } } - let localVarPath = `/status/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}/${this.configuration.encodeParam({name: "index", value: index, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; + let localVarPath = `/status/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}/${this.configuration.encodeParam({name: "index", value: index, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, diff --git a/libs/issuer-shared/src/lib/api/model/credential.ts b/libs/issuer-shared/src/lib/api/model/credential.ts index 7ad0578c..3c5498dc 100644 --- a/libs/issuer-shared/src/lib/api/model/credential.ts +++ b/libs/issuer-shared/src/lib/api/model/credential.ts @@ -14,5 +14,6 @@ export interface Credential { id: string; value: string; + sessionId: string; } diff --git a/libs/issuer-shared/src/lib/api/model/credentialOfferSessionEntity.ts b/libs/issuer-shared/src/lib/api/model/credentialOfferSessionEntity.ts new file mode 100644 index 00000000..f62e37f6 --- /dev/null +++ b/libs/issuer-shared/src/lib/api/model/credentialOfferSessionEntity.ts @@ -0,0 +1,42 @@ +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CredentialOfferSessionEntity { + id: string; + clientId?: string; + credentialOffer: object; + credentialDataSupplierInput?: object; + txCode?: object; + userPin?: string; + status: CredentialOfferSessionEntity.StatusEnum; + error?: string; + lastUpdatedAt: number; + notification_id: string; + issuerState?: string; + preAuthorizedCode?: string; + createdAt: number; +} +export namespace CredentialOfferSessionEntity { + export type StatusEnum = 'OFFER_CREATED' | 'OFFER_URI_RETRIEVED' | 'ACCESS_TOKEN_REQUESTED' | 'ACCESS_TOKEN_CREATED' | 'CREDENTIAL_REQUEST_RECEIVED' | 'CREDENTIAL_ISSUED' | 'ERROR'; + export const StatusEnum = { + OFFER_CREATED: 'OFFER_CREATED' as StatusEnum, + OFFER_URI_RETRIEVED: 'OFFER_URI_RETRIEVED' as StatusEnum, + ACCESS_TOKEN_REQUESTED: 'ACCESS_TOKEN_REQUESTED' as StatusEnum, + ACCESS_TOKEN_CREATED: 'ACCESS_TOKEN_CREATED' as StatusEnum, + CREDENTIAL_REQUEST_RECEIVED: 'CREDENTIAL_REQUEST_RECEIVED' as StatusEnum, + CREDENTIAL_ISSUED: 'CREDENTIAL_ISSUED' as StatusEnum, + ERROR: 'ERROR' as StatusEnum + }; +} + + diff --git a/libs/issuer-shared/src/lib/api/model/models.ts b/libs/issuer-shared/src/lib/api/model/models.ts index cc361b7d..b2858d66 100644 --- a/libs/issuer-shared/src/lib/api/model/models.ts +++ b/libs/issuer-shared/src/lib/api/model/models.ts @@ -3,10 +3,12 @@ export * from './createListDto'; export * from './credential'; export * from './credentialConfigurationSupportedV1013'; export * from './credentialOfferSession'; +export * from './credentialOfferSessionEntity'; export * from './credentialsSupportedDisplay'; export * from './imageInfo'; export * from './metadata'; export * from './metadataDisplay'; +export * from './sessionEntryDto'; export * from './sessionRequestDto'; export * from './sessionResponseDto'; export * from './statusList'; diff --git a/libs/issuer-shared/src/lib/api/model/sessionEntryDto.ts b/libs/issuer-shared/src/lib/api/model/sessionEntryDto.ts new file mode 100644 index 00000000..a0e8cb36 --- /dev/null +++ b/libs/issuer-shared/src/lib/api/model/sessionEntryDto.ts @@ -0,0 +1,20 @@ +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { Credential } from './credential'; +import { CredentialOfferSessionEntity } from './credentialOfferSessionEntity'; + + +export interface SessionEntryDto { + session: CredentialOfferSessionEntity; + credentials: Array; +} + diff --git a/libs/issuer-shared/src/lib/issuer.service.ts b/libs/issuer-shared/src/lib/issuer.service.ts index cfd49a72..771f8a23 100644 --- a/libs/issuer-shared/src/lib/issuer.service.ts +++ b/libs/issuer-shared/src/lib/issuer.service.ts @@ -56,10 +56,11 @@ export class IssuerService { this.sessionApiService.issuerControllerGetSession(id) ).then( (response) => { - if (this.statusEvent.value !== response.status) { - this.statusEvent.next(response.status); + if (this.statusEvent.value !== response.session.status) { + this.statusEvent.next(response.session.status); } - if (response.status === 'CREDENTIAL_ISSUED') clearInterval(this.loop); + if (response.session.status === 'CREDENTIAL_ISSUED') + clearInterval(this.loop); }, (err) => { console.error(err); diff --git a/libs/relying-party-shared/src/index.ts b/libs/relying-party-shared/src/index.ts index cdf808b8..8537aab4 100644 --- a/libs/relying-party-shared/src/index.ts +++ b/libs/relying-party-shared/src/index.ts @@ -1,10 +1,6 @@ export * from './lib/key/key.module'; export * from './lib/key/key.service'; export * from './lib/auth/auth.module'; -export * from './lib/crypto/crypto-implementation'; -export * from './lib/crypto/ed25519'; -export * from './lib/crypto/crypto.module'; -export * from './lib/crypto/crypto.service'; export * from './lib/db/db.module'; export * from './lib/did/did'; export * from './lib/db-states'; diff --git a/libs/relying-party-shared/src/lib/db-states.ts b/libs/relying-party-shared/src/lib/db-states.ts index c7959fe3..7ed41f48 100644 --- a/libs/relying-party-shared/src/lib/db-states.ts +++ b/libs/relying-party-shared/src/lib/db-states.ts @@ -78,7 +78,6 @@ export class DBStates implements IStateManager { if (!id) { throw Error('No id supplied'); } - console.log(JSON.stringify(stateValue, null, 2)); await this.repository.save(this.repository.create({ ...stateValue, id })); } diff --git a/libs/relying-party-shared/src/lib/key/filesystem-key.service.ts b/libs/relying-party-shared/src/lib/key/filesystem-key.service.ts index 1a30ebef..8c0d074e 100644 --- a/libs/relying-party-shared/src/lib/key/filesystem-key.service.ts +++ b/libs/relying-party-shared/src/lib/key/filesystem-key.service.ts @@ -12,8 +12,7 @@ import { KeyService } from './key.service'; import { Injectable } from '@nestjs/common'; import { Signer } from '@sd-jwt/types'; import { ConfigService } from '@nestjs/config'; -import { CryptoImplementation } from '../crypto/crypto-implementation'; -import { CryptoService } from '../crypto/crypto.service'; +import { CryptoImplementation, CryptoService } from '@credhub/backend'; //TODO: implement a vault integration like in the backend /** diff --git a/libs/relying-party-shared/src/lib/key/key.module.ts b/libs/relying-party-shared/src/lib/key/key.module.ts index 0c2569dc..faa60569 100644 --- a/libs/relying-party-shared/src/lib/key/key.module.ts +++ b/libs/relying-party-shared/src/lib/key/key.module.ts @@ -4,8 +4,7 @@ import * as Joi from 'joi'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { HttpModule, HttpService } from '@nestjs/axios'; import { VaultKeyService } from './vault-key.service'; -import { CryptoService } from '../crypto/crypto.service'; -import { CryptoModule } from '../crypto/crypto.module'; +import { CryptoModule, CryptoService } from '@credhub/backend'; export const KEY_VALIDATION_SCHEMA = { KM_TYPE: Joi.string().valid('file', 'vault').default('file'), diff --git a/libs/relying-party-shared/src/lib/key/vault-key.service.ts b/libs/relying-party-shared/src/lib/key/vault-key.service.ts index 99f02796..fdb7b857 100644 --- a/libs/relying-party-shared/src/lib/key/vault-key.service.ts +++ b/libs/relying-party-shared/src/lib/key/vault-key.service.ts @@ -5,7 +5,7 @@ import { firstValueFrom } from 'rxjs'; import { importSPKI, exportJWK, JWTHeaderParameters } from 'jose'; import { ConfigService } from '@nestjs/config'; import { JwtPayload, Signer } from '@sd-jwt/types'; -import { CryptoService, CryptoType } from '../crypto/crypto.service'; +import { CryptoService, CryptoType } from '@credhub/backend'; @Injectable() export class VaultKeyService extends KeyService { diff --git a/libs/testing/project.json b/libs/testing/project.json index 9e6377fb..9bb3db54 100644 --- a/libs/testing/project.json +++ b/libs/testing/project.json @@ -4,5 +4,6 @@ "sourceRoot": "libs/testing/src", "projectType": "library", "tags": [], + "// targets": "to see all targets run: nx show project testing --web", "targets": {} } diff --git a/package.json b/package.json index e3b1eacb..40e6486a 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@nestjs/schedule": "^4.0.2", "@nestjs/swagger": "^7.3.1", "@nestjs/typeorm": "^10.0.2", + "@sd-jwt/crypto-browser": "^0.7.2", "@sd-jwt/crypto-nodejs": "^0.7.2", "@sd-jwt/jwt-status-list": "^0.7.2", "@sd-jwt/sd-jwt-vc": "^0.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ca49f38..65d4896d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,9 @@ importers: '@nestjs/typeorm': specifier: ^10.0.2 version: 10.0.2(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14)(rxjs@7.8.1)(typeorm@0.3.20(pg@8.12.0)(sqlite3@5.1.7)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.14.13)(typescript@5.5.4))) + '@sd-jwt/crypto-browser': + specifier: ^0.7.2 + version: 0.7.2 '@sd-jwt/crypto-nodejs': specifier: ^0.7.2 version: 0.7.2 @@ -2944,6 +2947,9 @@ packages: resolution: {integrity: sha512-vix1GplUFc1A9H42r/yXkg7cKYthggyqZEwlFdsBbn4xdZNE+AHVF4N7kPa1pPxipwN3UIHd4XnQ5MJV15mhsQ==} engines: {node: '>=18'} + '@sd-jwt/crypto-browser@0.7.2': + resolution: {integrity: sha512-3EsFaVxgzWw/MguUKjMnW66kBv3NjErgdrf0wniyIAfKCi/njlJ+Zxlj9BW2Dmekiqdh2rH1JOPnCM3CZU9XUw==} + '@sd-jwt/crypto-nodejs@0.7.2': resolution: {integrity: sha512-7DHy1WBHwvXseiX+U7XA6jX4dX4Ins3Nxd12JhBSm+FJfIwU97FU/H0KlF6lLyi4a4nbY/O6U9wJjYI1PxA9sQ==} engines: {node: '>=18'} @@ -4304,8 +4310,8 @@ packages: resolution: {integrity: sha512-FpJFVewoeazHEz86vi6KaWq8+YYuX5RgmH6oVGDjeLS75n62qzjlwx1VQP4U2UeOPMNuUA3PYQQI6qkoSWVN8A==} engines: {node: '>=18'} - chromedriver@127.0.0: - resolution: {integrity: sha512-/Jla24iL0ly/EI7i/q0ukANkpAWvAkSHbvC7FtBZXFJXe6klH0n/XT56VSBcBYB1iMgWM8kPwqF8pb9gdqN9UA==} + chromedriver@127.0.2: + resolution: {integrity: sha512-mYfJ/8FqzsdFOs2rPiAI4y0suFnv78cRnzZK0MHdSfSIDeRPbqZz0rNX4lrXt14hXc9vqXa+a8cMxlrhWtXKSQ==} engines: {node: '>=18'} hasBin: true @@ -13957,6 +13963,8 @@ snapshots: '@sd-jwt/types': 0.7.2 '@sd-jwt/utils': 0.7.2 + '@sd-jwt/crypto-browser@0.7.2': {} + '@sd-jwt/crypto-nodejs@0.7.2': {} '@sd-jwt/decode@0.6.1': @@ -15695,7 +15703,7 @@ snapshots: chrome-webstore-upload@3.1.0: {} - chromedriver@127.0.0: + chromedriver@127.0.2: dependencies: '@testim/chrome-version': 1.1.4 axios: 1.7.2 @@ -18271,7 +18279,7 @@ snapshots: dependencies: jwk-to-pem: 2.0.5 optionalDependencies: - chromedriver: 127.0.0 + chromedriver: 127.0.2 transitivePeerDependencies: - debug - supports-color @@ -21323,17 +21331,6 @@ snapshots: optionalDependencies: webpack: 5.92.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.21.5)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.93.0)) - webpack-dev-middleware@7.2.1(webpack@5.93.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(webpack-cli@5.1.4)): - dependencies: - colorette: 2.0.20 - memfs: 4.11.0 - mime-types: 2.1.35 - on-finished: 2.4.1 - range-parser: 1.2.1 - schema-utils: 4.2.0 - optionalDependencies: - webpack: 5.93.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(webpack-cli@5.1.4) - webpack-dev-server@4.15.2(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.93.0))(webpack@5.93.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(webpack-cli@5.1.4)): dependencies: '@types/bonjour': 3.5.13 @@ -21405,7 +21402,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.2.1(webpack@5.93.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(webpack-cli@5.1.4)) + webpack-dev-middleware: 7.2.1(webpack@5.92.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.21.5)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.93.0))) ws: 8.18.0 optionalDependencies: webpack: 5.92.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.21.5)(webpack-cli@5.1.4(webpack-dev-server@5.0.4)(webpack@5.93.0)) diff --git a/tsconfig.base.json b/tsconfig.base.json index 2912c163..d60fff8e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { + "@credhub/backend": ["libs/backend/src/index.ts"], "@credhub/holder-shared": ["libs/holder-shared/src/index.ts"], "@credhub/issuer-shared": ["libs/issuer-shared/src/index.ts"], "@credhub/relying-party-frontend": [