Commit e013aaec authored by Hidde-Jan Jongsma's avatar Hidde-Jan Jongsma

Add connector registration in organization service

parent 7e829cbc
import { Module } from '@nestjs/common';
import { JolocomModule } from './jolocom/jolocom.module';
import { IrmaModule } from './irma/irma.module';
import { ConnectorsService } from './connectors.service';
......
......@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { JolocomService } from './jolocom/jolocom.service';
import { IrmaService } from './irma/irma.service';
import { ConnectorService } from './connector-service.interface';
import { Organization } from '../organizations/organization.entity';
@Injectable()
export class ConnectorsService {
......@@ -18,4 +19,12 @@ export class ConnectorsService {
getConnector(type: string) {
return this.connectors.find(connector => connector.type === type);
}
async registerOrganization(organization: Organization) {
await Promise.all(
this.connectors.map(
async connector => await connector.registerOrganization(organization),
),
);
}
}
......@@ -7,6 +7,7 @@ export class IrmaService implements ConnectorService {
type = 'irma';
async registerOrganization(organization: Organization) {
// We don't need to do anything for IRMA.
return;
}
}
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
ManyToOne,
Column,
} from 'typeorm';
import { JolocomWallet } from './jolocom-wallet.entity';
import { BaseMetadata } from 'cred-types-jolocom-core/js/types';
@Entity()
export class JolocomCredentialType extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne(
() => JolocomWallet,
wallet => wallet.credentialOffers,
)
wallet: JolocomWallet;
@Column({ unique: true })
type: string;
@Column()
name: string;
@Column('simple-json')
context: BaseMetadata['context'];
@Column('simple-json')
claimInterface: BaseMetadata['claimInterface'];
}
import {
BaseEntity,
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
OneToMany,
} from 'typeorm';
import { randomBytes } from 'crypto';
import { Organization } from '../../organizations/organization.entity';
import { JolocomCredentialType } from './jolocom-credential-type.entity';
const JOLOCOM_WALLET_SEED_BYTES = 32;
const JOLOCOM_WALLET_PASSWORD_BYTES = 16;
@Entity()
export class JolocomWallet extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ update: false })
encryptedSeedHex: string;
@Column({ update: false })
password: string;
@OneToOne(() => Organization)
@JoinColumn()
organization: Organization;
@OneToMany(
() => JolocomCredentialType,
credentialOffer => credentialOffer.wallet,
)
credentialOffers: JolocomCredentialType[];
static randomPassword() {
return randomBytes(JOLOCOM_WALLET_PASSWORD_BYTES).toString('hex');
}
static randomSeed() {
return randomBytes(JOLOCOM_WALLET_SEED_BYTES);
}
get encryptedSeed(): Buffer {
return Buffer.from(this.encryptedSeedHex, 'hex');
}
set encryptedSeed(seed: Buffer) {
this.encryptedSeedHex = seed.toString('hex');
}
}
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { JolocomService } from './jolocom.service';
import { JolocomWallet } from './jolocom-wallet.entity';
import { JolocomCredentialType } from './jolocom-credential-type.entity';
@Module({
imports: [TypeOrmModule.forFeature([JolocomWallet, JolocomCredentialType])],
providers: [JolocomService],
exports: [JolocomService],
})
......
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { JolocomLib } from 'jolocom-lib';
import { JolocomRegistry } from 'jolocom-lib/js/registries/jolocomRegistry';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConnectorService } from '../connector-service.interface';
import { Organization } from 'src/organizations/organization.entity';
import { Organization } from '../../organizations/organization.entity';
import { JolocomWallet } from './jolocom-wallet.entity';
import { JolocomCredentialType } from './jolocom-credential-type.entity';
@Injectable()
export class JolocomService implements ConnectorService {
type = 'jolocom';
private registry: JolocomRegistry;
private logger: Logger;
constructor(
@InjectRepository(JolocomWallet)
private walletRepository: Repository<JolocomWallet>,
) {
this.registry = JolocomLib.registries.jolocom.create();
this.logger = new Logger(JolocomService.name);
}
/* ConnectorService methods */
async registerOrganization(organization: Organization) {
return;
const wallet = await this.createWalletForOrganization(organization);
await this.fuelWallet(wallet);
await this.registerWallet(wallet);
}
async createWalletForOrganization(organization: Organization) {
this.logger.log(`Creating wallet for ${organization.name}`);
const seed = JolocomWallet.randomSeed();
const password = JolocomWallet.randomPassword();
const keyProvider = JolocomLib.KeyProvider.fromSeed(seed, password);
const wallet = new JolocomWallet();
wallet.organization = organization;
wallet.password = password;
wallet.encryptedSeedHex = keyProvider.encryptedSeed; // Already in hex format
await this.walletRepository.save(wallet);
this.logger.log(`Created wallet for ${organization.name}`);
return wallet;
}
async registerWallet(wallet: JolocomWallet) {
this.logger.log(`Wallet registration started`);
const keyProvider = this.getKeyProvider(wallet);
const identityWallet = await this.registry.create(
keyProvider,
wallet.password,
);
this.logger.log(`Wallet registration successful`);
// TODO: Maybe we shouldn't return here.
return identityWallet;
}
async getIdentityWallet(wallet: JolocomWallet) {
const keyProvider = this.getKeyProvider(wallet);
const identityWallet = await this.registry.authenticate(keyProvider, {
derivationPath: JolocomLib.KeyTypes.jolocomIdentityKey,
encryptionPass: wallet.password,
});
return identityWallet;
}
async fuelWallet(wallet: JolocomWallet) {
this.logger.log(`Wallet fueling started`);
const publicEthKey = this.getPublicEthKey(wallet);
await JolocomLib.util.fuelKeyWithEther(publicEthKey);
this.logger.log(`Wallet fueling started`);
}
getPublicEthKey(wallet: JolocomWallet) {
const keyProvider = this.getKeyProvider(wallet);
return keyProvider.getPublicKey({
encryptionPass: wallet.password,
derivationPath: JolocomLib.KeyTypes.ethereumKey,
});
}
getKeyProvider(wallet: JolocomWallet) {
return new JolocomLib.KeyProvider(wallet.encryptedSeed);
}
/* Jolocom specific */
// public async processCredentialIssueRequest(
// request: CredentialIssueRequest,
// response: Response,
// ): Promise<void> {
// const credOffer = await this.createCredentialOfferToken(request);
// const token = credOffer.encode();
// const viewData = {
// requestId: request.requestId,
// qr: await QRCode.toDataURL(token),
// };
// console.log('Jolocom CredentialOfferToken: ', credOffer.encode());
// return response.render('jolocom/issue', viewData);
// }
// public async processCredentialVerifyRequest(
// verifyRequest: CredentialVerifyRequest,
// response: Response,
// ): Promise<void> {
// // Create Jolocom interaction token
// const credRequestToken = await this.createCredentialRequestToken(
// verifyRequest,
// );
// const jwt = credRequestToken.encode();
// // Render interaction token (qr code) to user
// const viewData = {
// requestId: verifyRequest.requestId,
// qr: await QRCode.toDataURL(jwt),
// };
// return response.render('jolocom/verify', viewData);
// }
/**
* Instantiate a Jolocom IdentityWallet
* This wallet must already be registered on Etherium
*
* @param organization the organization for which an identifity wallet is instantiated
*/
// protected async getIdentityWallet(
// organization: Organization,
// ): Promise<IdentityWallet> {
// const password = organization.walletConfigs.jolocom!.password;
// const seed = Buffer.from(organization.walletConfigs.jolocom!.seed, 'hex');
// /**
// * From Jolocom Documentation:
// * You will need to instantiate a Key Provider using the seed used for identity creation
// * We are currently working on simplifying, and optimising this part of the api
// */
// const vaultedKeyProvider = JolocomLib.KeyProvider.fromSeed(seed, password);
// const registry = JolocomLib.registries.jolocom.create();
// return await registry.authenticate(vaultedKeyProvider, {
// derivationPath: JolocomLib.KeyTypes.jolocomIdentityKey,
// encryptionPass: password,
// });
// }
// /**
// * Construct a Jolocom CredentialOffer interaction token
// *
// * @param issueRequest the credential issue request
// */
// protected async createCredentialOfferToken(
// issueRequest: CredentialIssueRequest,
// ): Promise<JSONWebToken<CredentialOfferRequest>> {
// const issuer = issueRequest.getIssuer();
// const identityWallet = await this.getIdentityWallet(issuer);
// const { credentialOffers, password } = issuer.walletConfigs.jolocom!;
// // Get the Jolocom offeredType and metadata from configured list with credential offers
// const {
// schema: { type: offeredType },
// metadata = {},
// } = credentialOffers[issueRequest.credentialType]; // Use the credential type URI as defined in the CredentialIssueRequest
// // Return a Jolocom CredentialOffert interaction token
// return await identityWallet.create.interactionTokens.request.offer(
// {
// callbackURL: SSIServiceApp.getUrl(
// `/connectors/jolocom/issue/${issueRequest.requestId}`,
// ),
// offeredCredentials: [
// {
// type: offeredType[offeredType.length - 1],
// ...metadata,
// },
// ],
// },
// password,
// );
// }
// /**
// * Construct a Jolocom CredentialReceive interaction token
// * to actual issue the credential to the user's wallet app
// *
// * @param issueRequest the credential issue request
// * @param jolocomOfferResponse the Jolocom CredentialOfferResponse that is received from the user's wallet app
// */
// public async createCredential(
// issueRequest: CredentialIssueRequest,
// jolocomOfferResponse: JSONWebToken<JWTEncodable>,
// ): Promise<JSONWebToken<JWTEncodable>> {
// const issuer = issueRequest.getIssuer(); // the organization that wants to issue a credential
// const identityWallet = await this.getIdentityWallet(issuer); // jolocom identity wallet of the issuer
// const password = issuer.walletConfigs.jolocom!.password; // password to sign credential
// const subject = jolocomOfferResponse.issuer; // the wallet app that wants to receive a credential
// const credentialType = issuer.walletConfigs.jolocom!.credentialOffers[
// issueRequest.credentialType
// ];
// // Create actual credential (with data)
// const credential = await identityWallet.create.signedCredential(
// {
// metadata: credentialType.schema,
// claim: {
// ...issueRequest.credentialData,
// },
// subject: keyIdToDid(subject),
// },
// password,
// );
// // Wrap credential in Jolocom interaction token object
// return await identityWallet.create.interactionTokens.response.issue(
// {
// signedCredentials: [credential.toJSON()],
// },
// password,
// jolocomOfferResponse,
// );
// }
// /**
// * Construct a Jolocom CredentialRequest interaction token
// *
// * @param verifyRequest the credential verify request
// */
// protected async createCredentialRequestToken(
// verifyRequest: CredentialVerifyRequest,
// ): Promise<JSONWebToken<CredentialRequest>> {
// const verifier = verifyRequest.getVerifier();
// const identityWallet = await this.getIdentityWallet(verifier);
// const password = verifier.walletConfigs.jolocom!.password;
// const credentialRequestToken = await identityWallet.create.interactionTokens.request.share(
// {
// callbackURL: SSIServiceApp.getUrl(
// `/connectors/jolocom/verify/${verifyRequest.requestId}`,
// ),
// credentialRequirements: [
// {
// type: ['Credential', verifyRequest.credentialType],
// constraints: [
// // TODO: implement check on allowed issuers
// // constraintFunctions.is(
// // "issuer",
// // "did:jolo:ed19430d6e28057194870dc9b19c1ca2ad099ff090b52350add129f1049bb65d"
// // )
// ],
// },
// ],
// },
// password,
// );
// // Save CredentialRequest because it is needed to verify a CredentialResponse token in the
// // next step. See method receiveCredential() below
// Store.set(credentialRequestToken.nonce, credentialRequestToken.encode());
// return credentialRequestToken;
// }
// public async receiveCredential(
// verifyRequest: CredentialVerifyRequest,
// token: string,
// ): Promise<any> {
// const jolocomCredentialResponse = JolocomLib.parse.interactionToken.fromJWT(
// token,
// );
// // TODO: check if this is needed here or can be done in JolocomConnector.receiveCredential() method
// if (!JolocomLib.util.validateDigestable(jolocomCredentialResponse)) {
// throw new Error('Invalid signature');
// // res.status(401).send("Invalid signature on interaction token");
// }
// const identityWallet = await this.getIdentityWallet(
// verifyRequest.getVerifier(),
// );
// // Get the CredentialShareToken (issued in previous interaction step)
// const jolocomCredentialRequestJWT: string = Store.get(
// jolocomCredentialResponse.nonce,
// );
// const jolocomCredentialRequest: JSONWebToken<CredentialRequest> = JolocomLib.parse.interactionToken.fromJWT(
// jolocomCredentialRequestJWT,
// );
// // The validate method will ensure the response contains a valid signature, is not expired,
// // lists our did in the aud (audience) section, and contains the same jti (nonce) as the request.
// // TODO: move this to route middleware
// await identityWallet.validateJWT(
// jolocomCredentialResponse,
// jolocomCredentialRequest,
// );
// const credentialResponse = jolocomCredentialResponse.interactionToken as CredentialResponse;
// // We check against the request we created in a previous step
// const validResponse = credentialResponse.satisfiesRequest(
// jolocomCredentialRequest.interactionToken,
// );
// if (!validResponse) {
// throw new Error('Incorrect credential received');
// }
// // Validate the provided credentials
// const providedCredentials = credentialResponse.suppliedCredentials;
// const signatureValidationResults = await JolocomLib.util.validateDigestables(
// providedCredentials,
// );
// if (signatureValidationResults.every(result => result === true)) {
// // The credentials can be used
// const data = providedCredentials.map(credential => credential.toJSON());
// // Handle the data in the provided credentials
// return verifyRequest.processCredentialData(data);
// } else {
// throw new Error('Not all provided credentials are valid');
// }
// }
}
......@@ -4,9 +4,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { OrganizationsController } from './organizations.controller';
import { OrganizationsService } from './organizations.service';
import { Organization } from './organization.entity';
import { ConnectorsModule } from 'src/connectors/connectors.module';
@Module({
imports: [TypeOrmModule.forFeature([Organization])],
imports: [TypeOrmModule.forFeature([Organization]), ConnectorsModule],
controllers: [OrganizationsController],
providers: [OrganizationsService],
})
......
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Organization } from './organization.entity';
import { ConnectorsService } from 'src/connectors/connectors.service';
@Injectable()
export class OrganizationsService {
logger: Logger;
constructor(
@InjectRepository(Organization)
private organizationsRepository: Repository<Organization>,
) {}
private connectorsService: ConnectorsService,
) {
this.logger = new Logger(Organization.name);
}
findAll() {
async findAll() {
return this.organizationsRepository.find();
}
createFromName(name: string) {
async findByIdentifier(uuid: string) {
const results = await this.organizationsRepository.find({ take: 1 });
return results[0];
}
async createFromName(name: string) {
this.logger.log(`Creating organization with name ${name}`);
const organization = new Organization();
organization.name = name;
organization.sharedSecret = Organization.randomSecret();
return this.organizationsRepository.save(organization);
await this.organizationsRepository.save(organization);
await this.connectorsService.registerOrganization(organization);
this.logger.log(`Created organization (id: ${organization.id})`);
return organization;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment