Skip to content

Commit 1491f49

Browse files
committed
add unit tests for kms accounts with prool
1 parent b6852bc commit 1491f49

File tree

22 files changed

+846
-134
lines changed

22 files changed

+846
-134
lines changed

.env.test

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,13 @@ ENCRYPTION_PASSWORD="test"
55
ENABLE_KEYPAIR_AUTH="true"
66
ENABLE_HTTPS="true"
77
REDIS_URL="redis://127.0.0.1:6379/0"
8-
THIRDWEB_API_SECRET_KEY="my-thirdweb-secret-key"
8+
THIRDWEB_API_SECRET_KEY="my-thirdweb-secret-key"
9+
10+
TEST_AWS_KMS_KEY_ID=""
11+
TEST_AWS_KMS_ACCESS_KEY_ID=""
12+
TEST_AWS_KMS_SECRET_ACCESS_KEY=""
13+
TEST_AWS_KMS_REGION=""
14+
15+
TEST_GCP_KMS_RESOURCE_PATH=""
16+
TEST_GCP_KMS_EMAIL=""
17+
TEST_GCP_KMS_PK=""

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"pg": "^8.11.3",
7272
"prisma": "^5.14.0",
7373
"prom-client": "^15.1.3",
74+
"prool": "^0.0.16",
7475
"superjson": "^2.2.1",
7576
"thirdweb": "^5.58.4",
7677
"uuid": "^9.0.1",
Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
import type { PrismaTransaction } from "../../schema/prisma";
2-
import type { WalletType } from "../../schema/wallet";
32
import { encrypt } from "../../utils/crypto";
43
import { getPrismaWithPostgresTx } from "../client";
54

65
// TODO: Case on types by wallet type
7-
interface CreateWalletDetailsParams {
6+
type CreateWalletDetailsParams = {
87
pgtx?: PrismaTransaction;
98
address: string;
10-
type: WalletType;
119
label?: string;
10+
} & (
11+
| {
12+
type: "aws-kms";
13+
awsKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
14+
awsKmsArn: string;
1215

13-
// AWS KMS
14-
awsKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
15-
awsKmsArn?: string;
16+
awsKmsSecretAccessKey?: string; // will be encrypted and stored, pass plaintext to this function
17+
awsKmsAccessKeyId?: string;
18+
}
19+
| {
20+
type: "gcp-kms";
21+
gcpKmsResourcePath: string;
22+
gcpKmsKeyRingId?: string; // depcrecated and unused, todo: remove with next breaking change
23+
gcpKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
24+
gcpKmsKeyVersionId?: string; // depcrecated and unused, todo: remove with next breaking change
25+
gcpKmsLocationId?: string; // depcrecated and unused, todo: remove with next breaking change
1626

17-
awsKmsSecretAccessKey?: string; // encrypted
18-
awsKmsAccessKeyId?: string;
19-
20-
// GCP KMS
21-
gcpKmsResourcePath?: string;
22-
gcpKmsKeyRingId?: string; // depcrecated and unused, todo: remove with next breaking change
23-
gcpKmsKeyId?: string; // depcrecated and unused, todo: remove with next breaking change
24-
gcpKmsKeyVersionId?: string; // depcrecated and unused, todo: remove with next breaking change
25-
gcpKmsLocationId?: string; // depcrecated and unused, todo: remove with next breaking change
26-
27-
gcpApplicationCredentialPrivateKey?: string; // encrypted
28-
gcpApplicationCredentialEmail?: string;
29-
}
27+
gcpApplicationCredentialPrivateKey?: string; // encrypted
28+
gcpApplicationCredentialEmail?: string;
29+
}
30+
);
3031

3132
export const createWalletDetails = async ({
3233
pgtx,
@@ -46,19 +47,30 @@ export const createWalletDetails = async ({
4647
);
4748
}
4849

49-
return prisma.walletDetails.create({
50-
data: {
51-
...walletDetails,
52-
address: walletDetails.address.toLowerCase(),
50+
if (walletDetails.type === "aws-kms") {
51+
return prisma.walletDetails.create({
52+
data: {
53+
...walletDetails,
54+
address: walletDetails.address.toLowerCase(),
5355

54-
awsKmsSecretAccessKey: walletDetails.awsKmsSecretAccessKey
55-
? encrypt(walletDetails.awsKmsSecretAccessKey)
56-
: undefined,
57-
58-
gcpApplicationCredentialPrivateKey:
59-
walletDetails.gcpApplicationCredentialPrivateKey
60-
? encrypt(walletDetails.gcpApplicationCredentialPrivateKey)
56+
awsKmsSecretAccessKey: walletDetails.awsKmsSecretAccessKey
57+
? encrypt(walletDetails.awsKmsSecretAccessKey)
6158
: undefined,
62-
},
63-
});
59+
},
60+
});
61+
}
62+
63+
if (walletDetails.type === "gcp-kms") {
64+
return prisma.walletDetails.create({
65+
data: {
66+
...walletDetails,
67+
address: walletDetails.address.toLowerCase(),
68+
69+
gcpApplicationCredentialPrivateKey:
70+
walletDetails.gcpApplicationCredentialPrivateKey
71+
? encrypt(walletDetails.gcpApplicationCredentialPrivateKey)
72+
: undefined,
73+
},
74+
});
75+
}
6476
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- AlterTable
2+
ALTER TABLE "wallet_details" ADD COLUMN "awsKmsAccessKeyId" TEXT,
3+
ADD COLUMN "awsKmsSecretAccessKey" TEXT,
4+
ADD COLUMN "gcpApplicationCredentialEmail" TEXT,
5+
ADD COLUMN "gcpApplicationCredentialPrivateKey" TEXT;

src/prisma/schema.prisma

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ model Configuration {
3030
contractSubscriptionsRetryDelaySeconds String @default("10") @map("contractSubscriptionsRetryDelaySeconds")
3131
3232
// AWS
33-
awsAccessKeyId String? @map("awsAccessKeyId") // global config, precendence goes to WalletDetails
34-
awsSecretAccessKey String? @map("awsSecretAccessKey") // global config, precendence goes to WalletDetails
35-
awsRegion String? @map("awsRegion") // // global config, treat as "default", store in WalletDetails.awsKmsArn
33+
awsAccessKeyId String? @map("awsAccessKeyId") /// global config, precendence goes to WalletDetails
34+
awsSecretAccessKey String? @map("awsSecretAccessKey") /// global config, precendence goes to WalletDetails
35+
awsRegion String? @map("awsRegion") /// global config, treat as "default", store in WalletDetails.awsKmsArn
3636
// GCP
37-
gcpApplicationProjectId String? @map("gcpApplicationProjectId") // global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
38-
gcpKmsLocationId String? @map("gcpKmsLocationId") // global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
39-
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") // global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
40-
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") // global config, precendence goes to WalletDetails
41-
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") // global config, precendence goes to WalletDetails
37+
gcpApplicationProjectId String? @map("gcpApplicationProjectId") /// global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
38+
gcpKmsLocationId String? @map("gcpKmsLocationId") /// global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
39+
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") /// global config, treat as "defult", store in WalletDetails.gcpKmsResourcePath
40+
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") /// global config, precendence goes to WalletDetails
41+
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") /// global config, precendence goes to WalletDetails
4242
// Auth
4343
authDomain String @default("") @map("authDomain") // TODO: Remove defaults on major
4444
authWalletEncryptedJson String @default("") @map("authWalletEncryptedJson") // TODO: Remove defaults on major
@@ -82,18 +82,18 @@ model WalletDetails {
8282
// Local
8383
encryptedJson String? @map("encryptedJson")
8484
// KMS
85-
awsKmsKeyId String? @map("awsKmsKeyId") // deprecated and unused, todo: remove with next breaking change. Use awsKmsArn
85+
awsKmsKeyId String? @map("awsKmsKeyId") /// deprecated and unused, todo: remove with next breaking change. Use awsKmsArn
8686
awsKmsArn String? @map("awsKmsArn")
87-
awsKmsSecretAccessKey String? @map("awsKmsSecretAccessKey") // if not available, default to: Configuration.awsSecretAccessKey
88-
awsKmsAccessKeyId String? @map("awsKmsAccessKeyId") // if not available, default to: Configuration.awsAccessKeyId
87+
awsKmsSecretAccessKey String? @map("awsKmsSecretAccessKey") /// if not available, default to: Configuration.awsSecretAccessKey
88+
awsKmsAccessKeyId String? @map("awsKmsAccessKeyId") /// if not available, default to: Configuration.awsAccessKeyId
8989
// GCP
90-
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") @db.VarChar(50) // deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
91-
gcpKmsKeyId String? @map("gcpKmsKeyId") @db.VarChar(50) // deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
92-
gcpKmsKeyVersionId String? @map("gcpKmsKeyVersionId") @db.VarChar(20) // deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
93-
gcpKmsLocationId String? @map("gcpKmsLocationId") @db.VarChar(20) // deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
90+
gcpKmsKeyRingId String? @map("gcpKmsKeyRingId") @db.VarChar(50) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
91+
gcpKmsKeyId String? @map("gcpKmsKeyId") @db.VarChar(50) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
92+
gcpKmsKeyVersionId String? @map("gcpKmsKeyVersionId") @db.VarChar(20) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
93+
gcpKmsLocationId String? @map("gcpKmsLocationId") @db.VarChar(20) /// deprecated and unused. Use gcpKmsResourcePath instead, todo: remove with next breaking change
9494
gcpKmsResourcePath String? @map("gcpKmsResourcePath") @db.Text
95-
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") // if not available, default to: Configuration.gcpApplicationCredentialEmail
96-
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") // if not available, default to: Configuration.gcpApplicationCredentialPrivateKey
95+
gcpApplicationCredentialEmail String? @map("gcpApplicationCredentialEmail") /// if not available, default to: Configuration.gcpApplicationCredentialEmail
96+
gcpApplicationCredentialPrivateKey String? @map("gcpApplicationCredentialPrivateKey") /// if not available, default to: Configuration.gcpApplicationCredentialPrivateKey
9797
9898
@@map("wallet_details")
9999
}

src/server/routes/backend-wallet/create.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { WalletType } from "../../../schema/wallet";
55
import { getConfig } from "../../../utils/cache/getConfig";
6+
import { createCustomError } from "../../middleware/error";
67
import { AddressSchema } from "../../schemas/address";
78
import { standardResponseSchema } from "../../schemas/sharedApiSchemas";
8-
import { createAwsKmsWallet } from "../../utils/wallets/createAwsKmsWallet";
9-
import { createGcpKmsWallet } from "../../utils/wallets/createGcpKmsWallet";
9+
import {
10+
CreateAwsKmsWalletError,
11+
createAwsKmsWallet,
12+
} from "../../utils/wallets/createAwsKmsWallet";
13+
import {
14+
CreateGcpKmsWalletError,
15+
createGcpKmsWallet,
16+
} from "../../utils/wallets/createGcpKmsWallet";
1017
import { createLocalWallet } from "../../utils/wallets/createLocalWallet";
1118

1219
const requestBodySchema = Type.Object({
@@ -66,10 +73,32 @@ export const createBackendWallet = async (fastify: FastifyInstance) => {
6673
walletAddress = await createLocalWallet({ label });
6774
break;
6875
case WalletType.awsKms:
69-
walletAddress = await createAwsKmsWallet({ label });
76+
try {
77+
walletAddress = await createAwsKmsWallet({ label });
78+
} catch (e) {
79+
if (e instanceof CreateAwsKmsWalletError) {
80+
throw createCustomError(
81+
e.message,
82+
StatusCodes.BAD_REQUEST,
83+
"CREATE_AWS_KMS_WALLET_ERROR",
84+
);
85+
}
86+
throw e;
87+
}
7088
break;
7189
case WalletType.gcpKms:
72-
walletAddress = await createGcpKmsWallet({ label });
90+
try {
91+
walletAddress = await createGcpKmsWallet({ label });
92+
} catch (e) {
93+
if (e instanceof CreateGcpKmsWalletError) {
94+
throw createCustomError(
95+
e.message,
96+
StatusCodes.BAD_REQUEST,
97+
"CREATE_GCP_KMS_WALLET_ERROR",
98+
);
99+
}
100+
throw e;
101+
}
73102
break;
74103
}
75104

src/server/routes/backend-wallet/import.ts

Lines changed: 38 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,89 +10,71 @@ import { importAwsKmsWallet } from "../../utils/wallets/importAwsKmsWallet";
1010
import { importGcpKmsWallet } from "../../utils/wallets/importGcpKmsWallet";
1111
import { importLocalWallet } from "../../utils/wallets/importLocalWallet";
1212

13-
const RequestBodySchema = Type.Union([
13+
const RequestBodySchema = Type.Intersect([
1414
Type.Object({
1515
label: Type.Optional(
1616
Type.String({
1717
description: "Optional label for the imported wallet",
1818
}),
1919
),
20-
awsKmsArn: Type.String({
21-
description: "AWS KMS key ARN",
22-
examples: [
23-
"arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
24-
],
25-
}),
26-
credentials: Type.Object(
27-
{
28-
awsAccessKeyId: Type.String({ description: "AWS Access Key ID" }),
29-
awsSecretAccessKey: Type.String({
30-
description: "AWS Secret Access Key",
31-
}),
32-
},
33-
{
34-
description:
35-
"Optional AWS credentials to use for importing the wallet, if not provided, the default AWS credentials will be used (if available).",
36-
},
37-
),
3820
}),
39-
// TODO: with next breaking change, only require GCP KMS resource path
40-
Type.Object({
41-
label: Type.Optional(
42-
Type.String({
43-
description: "Optional label for the imported wallet",
21+
Type.Union([
22+
Type.Object({
23+
awsKmsArn: Type.String({
24+
description: "AWS KMS key ARN",
25+
examples: [
26+
"arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
27+
],
4428
}),
45-
),
46-
gcpKmsKeyId: Type.String({
47-
description: "GCP KMS key ID",
48-
examples: ["12345678-1234-1234-1234-123456789012"],
49-
}),
50-
gcpKmsKeyVersionId: Type.String({
51-
description: "GCP KMS key version ID",
52-
examples: ["1"],
53-
}),
54-
credentials: Type.Optional(
55-
Type.Object(
29+
credentials: Type.Object(
5630
{
57-
email: Type.String({ description: "GCP service account email" }),
58-
privateKey: Type.String({
59-
description: "GCP service account private key",
31+
awsAccessKeyId: Type.String({ description: "AWS Access Key ID" }),
32+
awsSecretAccessKey: Type.String({
33+
description: "AWS Secret Access Key",
6034
}),
6135
},
6236
{
6337
description:
64-
"Optional GCP credentials to use for importing the wallet, if not provided, the default GCP credentials will be used (if available).",
38+
"Optional AWS credentials to use for importing the wallet, if not provided, the default AWS credentials will be used (if available).",
6539
},
6640
),
67-
),
68-
}),
69-
Type.Union([
41+
}),
42+
// TODO: with next breaking change, only require GCP KMS resource path
7043
Type.Object({
71-
label: Type.Optional(
72-
Type.String({
73-
description: "Optional label for the imported wallet",
74-
}),
44+
gcpKmsKeyId: Type.String({
45+
description: "GCP KMS key ID",
46+
examples: ["12345678-1234-1234-1234-123456789012"],
47+
}),
48+
gcpKmsKeyVersionId: Type.String({
49+
description: "GCP KMS key version ID",
50+
examples: ["1"],
51+
}),
52+
credentials: Type.Optional(
53+
Type.Object(
54+
{
55+
email: Type.String({ description: "GCP service account email" }),
56+
privateKey: Type.String({
57+
description: "GCP service account private key",
58+
}),
59+
},
60+
{
61+
description:
62+
"Optional GCP credentials to use for importing the wallet, if not provided, the default GCP credentials will be used (if available).",
63+
},
64+
),
7565
),
66+
}),
67+
Type.Object({
7668
privateKey: Type.String({
7769
description: "The private key of the wallet to import",
7870
}),
7971
}),
8072
Type.Object({
81-
label: Type.Optional(
82-
Type.String({
83-
description: "Optional label for the imported wallet",
84-
}),
85-
),
8673
mnemonic: Type.String({
8774
description: "The mnemonic phrase of the wallet to import",
8875
}),
8976
}),
9077
Type.Object({
91-
label: Type.Optional(
92-
Type.String({
93-
description: "Optional label for the imported wallet",
94-
}),
95-
),
9678
encryptedJson: Type.String({
9779
description: "The encrypted JSON of the wallet to import",
9880
}),

src/server/routes/configuration/wallets/get.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ export const responseBodySchema = Type.Object({
99
result: Type.Object({
1010
type: Type.Enum(WalletType),
1111

12-
awsAccessKeyId: Type.String().Optional(),
13-
awsRegion: Type.String().Optional(),
12+
awsAccessKeyId: Type.Union([Type.String(), Type.Null()]),
13+
awsRegion: Type.Union([Type.String(), Type.Null()]),
1414

1515
// Omit awsSecretAccessKey
16-
gcpApplicationProjectId: Type.String().Optional(),
17-
gcpKmsLocationId: Type.String().Optional(),
18-
gcpKmsKeyRingId: Type.String().Optional(),
19-
gcpApplicationCredentialEmail: Type.String().Optional(),
16+
gcpApplicationProjectId: Type.Union([Type.String(), Type.Null()]),
17+
gcpKmsLocationId: Type.Union([Type.String(), Type.Null()]),
18+
gcpKmsKeyRingId: Type.Union([Type.String(), Type.Null()]),
19+
gcpApplicationCredentialEmail: Type.Union([Type.String(), Type.Null()]),
2020
// Omit gcpApplicationCredentialPrivateKey
2121
}),
2222
});

0 commit comments

Comments
 (0)