Skip to content

Commit 62c7e93

Browse files
feat: add jwt authorization to supply chain example
closes #1579 Signed-off-by: Elena Izaguirre <[email protected]>
1 parent 46b1c63 commit 62c7e93

File tree

10 files changed

+155
-20
lines changed

10 files changed

+155
-20
lines changed

README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ As blockchain technology proliferates, blockchain integration will become an inc
3434
-p 4200:4200 \
3535
ghcr.io/hyperledger/cactus-example-supply-chain-app:2021-09-08--docs-1312
3636
```
37-
3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3100`
38-
4. Visit http://localhost:3100 in a web browser with Javascript enabled
39-
5. Use the graphical user interface to create data on both ledgers and observe that a consistent view of the data from different ledgers is provided.
37+
3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3200`
38+
4. Token generated by the application is displayed below
39+
5. Visit http://localhost:3200 in a web browser with Javascript enabled and insert the token when prompted
40+
6. Use the graphical user interface to create data on both ledgers and observe that a consistent view of the data from different ledgers is provided.
4041

4142
Once the last command has finished executing, open link printed on the console with a web browser of your choice
4243

examples/cactus-example-supply-chain-backend/src/main/typescript/supply-chain-app.ts

+96-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { AddressInfo } from "net";
22
import { Server } from "http";
33

4-
import { exportPKCS8, generateKeyPair, KeyLike, exportSPKI } from "jose";
4+
import {
5+
exportPKCS8,
6+
generateKeyPair,
7+
KeyLike,
8+
exportSPKI,
9+
SignJWT,
10+
} from "jose";
11+
import expressJwt from "express-jwt";
12+
513
import { v4 as uuidv4 } from "uuid";
614
import exitHook, { IAsyncExitHookDoneCallback } from "async-exit-hook";
715

@@ -24,7 +32,12 @@ import {
2432
Servers,
2533
} from "@hyperledger/cactus-common";
2634

27-
import { ApiServer, ConfigService } from "@hyperledger/cactus-cmd-api-server";
35+
import {
36+
ApiServer,
37+
AuthorizationProtocol,
38+
ConfigService,
39+
IAuthorizationConfig,
40+
} from "@hyperledger/cactus-cmd-api-server";
2841

2942
import { PluginConsortiumManual } from "@hyperledger/cactus-plugin-consortium-manual";
3043
import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory";
@@ -73,6 +86,8 @@ export class SupplyChainApp {
7386
private _besuApiClient?: BesuApi;
7487
private _quorumApiClient?: QuorumApi;
7588
private _fabricApiClient?: FabricApi;
89+
private authorizationConfig?: IAuthorizationConfig;
90+
private token?: string;
7691

7792
public get besuApiClientOrThrow(): BesuApi {
7893
if (this._besuApiClient) {
@@ -130,6 +145,48 @@ export class SupplyChainApp {
130145
this.shutdownHooks = [];
131146
}
132147

148+
async getOrCreateToken(): Promise<string> {
149+
if (!this.token) {
150+
await this.createAuthorizationConfig();
151+
}
152+
return this.token as string;
153+
}
154+
155+
async getOrCreateAuthorizationConfig(): Promise<IAuthorizationConfig> {
156+
if (!this.authorizationConfig) {
157+
await this.createAuthorizationConfig();
158+
}
159+
return this.authorizationConfig as IAuthorizationConfig;
160+
}
161+
162+
async createAuthorizationConfig(): Promise<void> {
163+
const jwtKeyPair = await generateKeyPair("RS256", { modulusLength: 4096 });
164+
const jwtPrivateKeyPem = await exportPKCS8(jwtKeyPair.privateKey);
165+
const expressJwtOptions: expressJwt.Options = {
166+
algorithms: ["RS256"],
167+
secret: jwtPrivateKeyPem,
168+
audience: uuidv4(),
169+
issuer: uuidv4(),
170+
};
171+
172+
const jwtPayload = { name: "Peter", location: "London" };
173+
this.token = await new SignJWT(jwtPayload)
174+
.setProtectedHeader({
175+
alg: "RS256",
176+
})
177+
.setIssuer(expressJwtOptions.issuer)
178+
.setAudience(expressJwtOptions.audience)
179+
.sign(jwtKeyPair.privateKey);
180+
181+
this.authorizationConfig = {
182+
unprotectedEndpointExemptions: [],
183+
expressJwtOptions,
184+
socketIoJwtOptions: {
185+
secret: jwtPrivateKeyPem,
186+
},
187+
};
188+
}
189+
133190
public async start(): Promise<IStartInfo> {
134191
this.log.debug(`Starting SupplyChainApp...`);
135192

@@ -174,9 +231,21 @@ export class SupplyChainApp {
174231
const addressInfoC = httpApiC.address() as AddressInfo;
175232
const nodeApiHostC = `http://localhost:${addressInfoC.port}`;
176233

177-
const besuConfig = new Configuration({ basePath: nodeApiHostA });
178-
const quorumConfig = new Configuration({ basePath: nodeApiHostB });
179-
const fabricConfig = new Configuration({ basePath: nodeApiHostC });
234+
const token = await this.getOrCreateToken();
235+
const baseOptions = { headers: { Authorization: `Bearer ${token}` } };
236+
237+
const besuConfig = new Configuration({
238+
basePath: nodeApiHostA,
239+
baseOptions,
240+
});
241+
const quorumConfig = new Configuration({
242+
basePath: nodeApiHostB,
243+
baseOptions,
244+
});
245+
const fabricConfig = new Configuration({
246+
basePath: nodeApiHostC,
247+
baseOptions,
248+
});
180249

181250
const besuApiClient = new BesuApi(besuConfig);
182251
const quorumApiClient = new QuorumApi(quorumConfig);
@@ -217,6 +286,11 @@ export class SupplyChainApp {
217286
consortiumDatabase,
218287
keyPairPem: keyPairPemA,
219288
logLevel: this.options.logLevel,
289+
ctorArgs: {
290+
baseOptions: {
291+
headers: { Authorization: `Bearer ${token}` },
292+
},
293+
},
220294
}),
221295
new SupplyChainCactusPlugin({
222296
logLevel: this.options.logLevel,
@@ -256,6 +330,11 @@ export class SupplyChainApp {
256330
consortiumDatabase,
257331
keyPairPem: keyPairPemB,
258332
logLevel: this.options.logLevel,
333+
ctorArgs: {
334+
baseOptions: {
335+
headers: { Authorization: `Bearer ${token}` },
336+
},
337+
},
259338
}),
260339
new SupplyChainCactusPlugin({
261340
logLevel: this.options.logLevel,
@@ -294,6 +373,11 @@ export class SupplyChainApp {
294373
consortiumDatabase,
295374
keyPairPem: keyPairPemC,
296375
logLevel: "INFO",
376+
ctorArgs: {
377+
baseOptions: {
378+
headers: { Authorization: `Bearer ${token}` },
379+
},
380+
},
297381
}),
298382
new SupplyChainCactusPlugin({
299383
logLevel: "INFO",
@@ -333,6 +417,8 @@ export class SupplyChainApp {
333417

334418
const apiServerC = await this.startNode(httpApiC, httpGuiC, registryC);
335419

420+
this.log.info(`JWT generated by the application: ${token}`);
421+
336422
return {
337423
apiServerA,
338424
apiServerB,
@@ -341,13 +427,13 @@ export class SupplyChainApp {
341427
fabricApiClient,
342428
quorumApiClient,
343429
supplyChainApiClientA: new SupplyChainApi(
344-
new Configuration({ basePath: nodeApiHostA }),
430+
new Configuration({ basePath: nodeApiHostA, baseOptions }),
345431
),
346432
supplyChainApiClientB: new SupplyChainApi(
347-
new Configuration({ basePath: nodeApiHostA }),
433+
new Configuration({ basePath: nodeApiHostA, baseOptions }),
348434
),
349435
supplyChainApiClientC: new SupplyChainApi(
350-
new Configuration({ basePath: nodeApiHostA }),
436+
new Configuration({ basePath: nodeApiHostA, baseOptions }),
351437
),
352438
};
353439
}
@@ -498,6 +584,8 @@ export class SupplyChainApp {
498584
properties.cockpitPort = addressInfoCockpit.port;
499585
properties.grpcPort = 0; // TODO - make this configurable as well
500586
properties.logLevel = this.options.logLevel || "INFO";
587+
properties.authorizationProtocol = AuthorizationProtocol.JSON_WEB_TOKEN;
588+
properties.authorizationConfigJson = await this.getOrCreateAuthorizationConfig();
501589

502590
const apiServer = new ApiServer({
503591
config: properties,

examples/cactus-example-supply-chain-frontend/src/app/app.module.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,26 @@ import {
1717
FABRIC_DEMO_LEDGER_ID,
1818
} from "src/constants";
1919
import { ApiClient } from "@hyperledger/cactus-api-client";
20+
import { AuthConfig } from "./common/auth-config";
2021

2122
LoggerProvider.setLogLevel("TRACE");
2223

2324
const log: Logger = LoggerProvider.getOrCreate({ label: "app-module" });
2425

26+
let token = "";
27+
while (token == "") {
28+
token = window.prompt("Introduce the token generated by the application");
29+
}
30+
AuthConfig.authToken = token;
31+
log.info(`Inserted token: ${AuthConfig.authToken}`);
32+
2533
log.info("Running AppModule...");
2634
const cactusApiUrl = location.origin;
2735
log.info("Instantiating ApiClient with CACTUS_API_URL=%o", cactusApiUrl);
28-
const configuration = new Configuration({ basePath: cactusApiUrl });
36+
const configuration = new Configuration({
37+
basePath: cactusApiUrl,
38+
baseOptions: { headers: { Authorization: `Bearer ${AuthConfig.authToken}` } },
39+
});
2940
const apiClient = new ApiClient(configuration);
3041

3142
@NgModule({

examples/cactus-example-supply-chain-frontend/src/app/bamboo-harvest/bamboo-harvest-list/bamboo-harvest-list.page.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { QUORUM_DEMO_LEDGER_ID } from "../../../constants";
1111
import { BambooHarvestDetailPage } from "../bamboo-harvest-detail/bamboo-harvest-detail.page";
1212
import { ModalController } from "@ionic/angular";
1313

14+
import { AuthConfig } from "../../common/auth-config";
15+
1416
@Component({
1517
selector: "app-bamboo-harvest-list",
1618
templateUrl: "./bamboo-harvest-list.page.html",
@@ -42,7 +44,11 @@ export class BambooHarvestListPage implements OnInit {
4244
this._supplyChainApi = await this.baseClient.ofLedger(
4345
this.quorumLedgerId,
4446
SupplyChainApi,
45-
{},
47+
{
48+
baseOptions: {
49+
headers: { Authorization: `Bearer ${AuthConfig.authToken}` },
50+
},
51+
},
4652
);
4753
await this.loadData();
4854
}

examples/cactus-example-supply-chain-frontend/src/app/bookshelf/bookshelf-detail/bookshelf-detail.page.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
import { Logger, LoggerProvider } from "@hyperledger/cactus-common";
1616
import { QUORUM_DEMO_LEDGER_ID } from "src/constants";
1717

18+
import { AuthConfig } from "../../common/auth-config";
19+
1820
@Component({
1921
selector: "app-bookshelf-detail",
2022
templateUrl: "./bookshelf-detail.page.html",
@@ -52,7 +54,11 @@ export class BookshelfDetailPage implements OnInit {
5254
this._supplyChainApi = await this.baseClient.ofLedger(
5355
this.quorumLedgerId,
5456
SupplyChainApi,
55-
{},
57+
{
58+
baseOptions: {
59+
headers: { Authorization: `Bearer ${AuthConfig.authToken}` },
60+
},
61+
},
5662
);
5763

5864
if (!this.bookshelf) {

examples/cactus-example-supply-chain-frontend/src/app/bookshelf/bookshelf-list/bookshelf-list.page.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { BESU_DEMO_LEDGER_ID } from "../../../constants";
1111
import { BookshelfDetailPage } from "../bookshelf-detail/bookshelf-detail.page";
1212
import { ModalController } from "@ionic/angular";
1313

14+
import { AuthConfig } from "../../common/auth-config";
15+
1416
@Component({
1517
selector: "app-bookshelf-list",
1618
templateUrl: "./bookshelf-list.page.html",
@@ -42,7 +44,11 @@ export class BookshelfListPage implements OnInit {
4244
this._supplyChainApi = await this.baseClient.ofLedger(
4345
this.ledgerId,
4446
SupplyChainApi,
45-
{},
47+
{
48+
baseOptions: {
49+
headers: { Authorization: `Bearer ${AuthConfig.authToken}` },
50+
},
51+
},
4652
);
4753
await this.loadData();
4854
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export class AuthConfig {
2+
static authToken: string;
3+
}

examples/cactus-example-supply-chain-frontend/src/app/shipment/shipment-detail/shipment-detail.page.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
import { Logger, LoggerProvider } from "@hyperledger/cactus-common";
1616
import { QUORUM_DEMO_LEDGER_ID } from "src/constants";
1717

18+
import { AuthConfig } from "../../common/auth-config";
19+
1820
@Component({
1921
selector: "app-shipment-detail",
2022
templateUrl: "./shipment-detail.page.html",
@@ -52,7 +54,11 @@ export class ShipmentDetailPage implements OnInit {
5254
this._supplyChainApi = await this.baseClient.ofLedger(
5355
this.quorumLedgerId,
5456
SupplyChainApi,
55-
{},
57+
{
58+
baseOptions: {
59+
headers: { Authorization: `Bearer ${AuthConfig.authToken}` },
60+
},
61+
},
5662
);
5763

5864
if (!this.shipment) {

examples/cactus-example-supply-chain-frontend/src/app/shipment/shipment-list/shipment-list.page.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { BESU_DEMO_LEDGER_ID } from "../../../constants";
1111
import { ShipmentDetailPage } from "../shipment-detail/shipment-detail.page";
1212
import { ModalController } from "@ionic/angular";
1313

14+
import { AuthConfig } from "../../common/auth-config";
15+
1416
@Component({
1517
selector: "app-shipment-list",
1618
templateUrl: "./shipment-list.page.html",
@@ -42,7 +44,11 @@ export class ShipmentListPage implements OnInit {
4244
this._supplyChainApi = await this.baseClient.ofLedger(
4345
this.ledgerId,
4446
SupplyChainApi,
45-
{},
47+
{
48+
baseOptions: {
49+
headers: { Authorization: `Bearer ${AuthConfig.authToken}` },
50+
},
51+
},
4652
);
4753
await this.loadData();
4854
}

examples/supply-chain-app/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
2. Observe the example application pulling up in the logs
2020
1. the test ledger containers,
2121
2. a test consortium with multiple members and their Cactus nodes
22-
3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3100`
23-
4. Visit http://0.0.0.0:3100 in your web browser with Javascript enabled
22+
3. Wait for the output to show the message `INFO (api-server): Cactus Cockpit reachable http://0.0.0.0:3200`
23+
4. Visit http://0.0.0.0:3200 in your web browser with Javascript enabled
2424

2525
## Building and running the container locally
2626

@@ -61,4 +61,6 @@ On the terminal, issue the following commands (steps 1 to 6) and then perform th
6161
7. Locate the `.vscode/template.launch.json` file
6262
8. Within that file locate the entry named `"Example: Supply Chain App"`
6363
9. Copy the VSCode debug definition object from 2) to your `.vscode/launch.json` file
64-
10. At this point the VSCode `Run and Debug` panel on the left should have an option also titled `"Example: Supply Chain App"` which
64+
10. At this point the VSCode `Run and Debug` panel on the left should have an option also titled `"Example: Supply Chain App"` which starts the application
65+
11. When the application finishes loading, token generated is displayed on the terminal
66+
12. Visit http://localhost:3200 in a web browser with Javascript enabled and insert the token when prompted

0 commit comments

Comments
 (0)