Skip to content

Commit 1246024

Browse files
authored
[Breaking] Use encryption password instead of secret key for encryption (#281)
* Migrate to ENCRYPTION_PASSWORD instead of THIRDWEB_API_SECRET_KEY for encryption * Update README.md * Update * Add encryption migration for auth wallet * Update auth checks * Fix bugs and add logs * Remove README.md * README.md
1 parent 11f30b5 commit 1246024

File tree

9 files changed

+198
-57
lines changed

9 files changed

+198
-57
lines changed

docs/1-user-guide.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------- |
99
| `THIRDWEB_API_SECRET_KEY` | thirdweb Api Secret Key (get it from thirdweb.com/dashboard) | ||
1010
| `POSTGRES_CONNECTION_URL` | PostgreSQL Connection string | postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable ||
11-
| `ADMIN_WALLET_ADDRESS` | The initial admin wallet address that can connect to this engine instance from the thirdweb dashboard for setup. | ||
11+
| `ADMIN_WALLET_ADDRESS` | The initial admin wallet address that can connect to this engine instance from the thirdweb dashboard for setup. |
12+
| `ENCRYPTION_PASSWORD` | A password used to encrypt sensitive wallet data for security. | ||
1213
| `HOST` | Host name of the API Server | `localhost` ||
1314
| `PORT` | Port number of the API Server | `3005` ||
1415
| `CHAIN_OVERRIDES` | Pass your own RPC urls to override the default ones. This can be file or an URL. See example override-rpc-urls.json | ||

src/db/configuration/getConfiguration.ts

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { LocalWallet } from "@thirdweb-dev/wallets";
33
import { WalletType } from "../../schema/wallet";
44
import { decrypt } from "../../utils/crypto";
55
import { env } from "../../utils/env";
6+
import { logger } from "../../utils/logger";
67
import { prisma } from "../client";
78
import { updateConfiguration } from "./updateConfiguration";
89

@@ -27,35 +28,94 @@ interface Config extends Configuration {
2728
};
2829
}
2930

30-
const withWalletConfig = (config: Configuration): Config => {
31+
const withWalletConfig = async (config: Configuration): Promise<Config> => {
32+
// TODO: Remove backwards compatibility with next breaking change
33+
if (config.awsAccessKeyId && config.awsSecretAccessKey && config.awsRegion) {
34+
// First try to load the aws secret using the encryption password
35+
let awsSecretAccessKey = decrypt(
36+
config.awsSecretAccessKey,
37+
env.ENCRYPTION_PASSWORD,
38+
);
39+
40+
// If that fails, try to load the aws secret using the thirdweb api secret key
41+
if (!awsSecretAccessKey) {
42+
awsSecretAccessKey = decrypt(
43+
config.awsSecretAccessKey,
44+
env.THIRDWEB_API_SECRET_KEY,
45+
);
46+
47+
// If that succeeds, update the configuration with the encryption password instead
48+
if (awsSecretAccessKey) {
49+
logger.worker.info(
50+
`[Encryption] Updating awsSecretAccessKey to use ENCRYPTION_PASSWORD`,
51+
);
52+
await updateConfiguration({
53+
awsSecretAccessKey,
54+
});
55+
}
56+
}
57+
58+
return {
59+
...config,
60+
walletConfiguration: {
61+
type: WalletType.awsKms,
62+
awsRegion: config.awsRegion,
63+
awsAccessKeyId: config.awsAccessKeyId,
64+
awsSecretAccessKey,
65+
},
66+
};
67+
}
68+
69+
// TODO: Remove backwards compatibility with next breaking change
70+
if (
71+
config.gcpApplicationProjectId &&
72+
config.gcpKmsLocationId &&
73+
config.gcpKmsKeyRingId &&
74+
config.gcpApplicationCredentialEmail &&
75+
config.gcpApplicationCredentialPrivateKey
76+
) {
77+
// First try to load the gcp secret using the encryption password
78+
let gcpApplicationCredentialPrivateKey = decrypt(
79+
config.gcpApplicationCredentialPrivateKey,
80+
env.ENCRYPTION_PASSWORD,
81+
);
82+
83+
// If that fails, try to load the gcp secret using the thirdweb api secret key
84+
if (!gcpApplicationCredentialPrivateKey) {
85+
gcpApplicationCredentialPrivateKey = decrypt(
86+
config.gcpApplicationCredentialPrivateKey,
87+
env.THIRDWEB_API_SECRET_KEY,
88+
);
89+
90+
// If that succeeds, update the configuration with the encryption password instead
91+
if (gcpApplicationCredentialPrivateKey) {
92+
logger.worker.info(
93+
`[Encryption] Updating gcpApplicationCredentialPrivateKey to use ENCRYPTION_PASSWORD`,
94+
);
95+
await updateConfiguration({
96+
gcpApplicationCredentialPrivateKey,
97+
});
98+
}
99+
}
100+
101+
return {
102+
...config,
103+
walletConfiguration: {
104+
type: WalletType.gcpKms,
105+
gcpApplicationProjectId: config.gcpApplicationProjectId,
106+
gcpKmsLocationId: config.gcpKmsLocationId,
107+
gcpKmsKeyRingId: config.gcpKmsKeyRingId,
108+
gcpApplicationCredentialEmail: config.gcpApplicationCredentialEmail,
109+
gcpApplicationCredentialPrivateKey,
110+
},
111+
};
112+
}
113+
31114
return {
32115
...config,
33-
walletConfiguration:
34-
config.awsAccessKeyId && config.awsSecretAccessKey && config.awsRegion
35-
? {
36-
type: WalletType.awsKms,
37-
awsAccessKeyId: config.awsAccessKeyId,
38-
awsSecretAccessKey: decrypt(config.awsSecretAccessKey),
39-
awsRegion: config.awsRegion,
40-
}
41-
: config.gcpApplicationProjectId &&
42-
config.gcpKmsLocationId &&
43-
config.gcpKmsKeyRingId &&
44-
config.gcpApplicationCredentialEmail &&
45-
config.gcpApplicationCredentialPrivateKey
46-
? {
47-
type: WalletType.gcpKms,
48-
gcpApplicationProjectId: config.gcpApplicationProjectId,
49-
gcpKmsLocationId: config.gcpKmsLocationId,
50-
gcpKmsKeyRingId: config.gcpKmsKeyRingId,
51-
gcpApplicationCredentialEmail: config.gcpApplicationCredentialEmail,
52-
gcpApplicationCredentialPrivateKey: decrypt(
53-
config.gcpApplicationCredentialPrivateKey,
54-
),
55-
}
56-
: {
57-
type: WalletType.local,
58-
},
116+
walletConfiguration: {
117+
type: WalletType.local,
118+
},
59119
};
60120
};
61121

@@ -64,7 +124,7 @@ const createAuthWalletEncryptedJson = async () => {
64124
await wallet.generate();
65125
return wallet.export({
66126
strategy: "encryptedJson",
67-
password: env.THIRDWEB_API_SECRET_KEY,
127+
password: env.ENCRYPTION_PASSWORD,
68128
});
69129
};
70130

src/server/middleware/auth.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import { AsyncWallet } from "@thirdweb-dev/wallets/evm/wallets/async";
99
import { utils } from "ethers";
1010
import { FastifyInstance } from "fastify";
1111
import { getConfiguration } from "../../db/configuration/getConfiguration";
12+
import { updateConfiguration } from "../../db/configuration/updateConfiguration";
1213
import { getPermissions } from "../../db/permissions/getPermissions";
1314
import { createToken } from "../../db/tokens/createToken";
1415
import { getToken } from "../../db/tokens/getToken";
1516
import { revokeToken } from "../../db/tokens/revokeToken";
1617
import { env } from "../../utils/env";
18+
import { logger } from "../../utils/logger";
1719
import { Permission } from "../schemas/auth";
1820

1921
export type TAuthData = never;
@@ -83,10 +85,33 @@ export const withAuth = async (server: FastifyInstance) => {
8385
getSigner: async () => {
8486
const config = await getConfiguration();
8587
const wallet = new LocalWallet();
86-
await wallet.import({
87-
encryptedJson: config.authWalletEncryptedJson,
88-
password: env.THIRDWEB_API_SECRET_KEY,
89-
});
88+
89+
try {
90+
// First, we try to load the wallet with the encryption password
91+
await wallet.import({
92+
encryptedJson: config.authWalletEncryptedJson,
93+
password: env.ENCRYPTION_PASSWORD,
94+
});
95+
} catch {
96+
// If that fails, we try to load the wallet with the secret key
97+
await wallet.import({
98+
encryptedJson: config.authWalletEncryptedJson,
99+
password: env.THIRDWEB_API_SECRET_KEY,
100+
});
101+
102+
// And then update the auth wallet to use encryption password instead
103+
const encryptedJson = await wallet.export({
104+
strategy: "encryptedJson",
105+
password: env.ENCRYPTION_PASSWORD,
106+
});
107+
108+
logger.worker.info(
109+
`[Encryption] Updating authWalletEncryptedJson to use ENCRYPTION_PASSWORD`,
110+
);
111+
await updateConfiguration({
112+
authWalletEncryptedJson: encryptedJson,
113+
});
114+
}
90115

91116
return wallet.getSigner();
92117
},
@@ -114,7 +139,12 @@ export const withAuth = async (server: FastifyInstance) => {
114139
}
115140

116141
const { payload } = parseJWT(jwt);
117-
await revokeToken({ id: payload.jti });
142+
143+
try {
144+
await revokeToken({ id: payload.jti });
145+
} catch {
146+
logger.worker.error(`[Auth] Failed to revoke token ${payload.jti}`);
147+
}
118148
},
119149
},
120150
});

src/server/routes/auth/access-tokens/create.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LocalWallet } from "@thirdweb-dev/wallets";
44
import { FastifyInstance } from "fastify";
55
import { StatusCodes } from "http-status-codes";
66
import { getConfiguration } from "../../../../db/configuration/getConfiguration";
7+
import { updateConfiguration } from "../../../../db/configuration/updateConfiguration";
78
import { createToken } from "../../../../db/tokens/createToken";
89
import { env } from "../../../../utils/env";
910
import { AccessTokenSchema } from "./getAll";
@@ -43,10 +44,31 @@ export async function createAccessToken(fastify: FastifyInstance) {
4344

4445
const config = await getConfiguration();
4546
const wallet = new LocalWallet();
46-
await wallet.import({
47-
encryptedJson: config.authWalletEncryptedJson,
48-
password: env.THIRDWEB_API_SECRET_KEY,
49-
});
47+
48+
// TODO: Remove this with next breaking change
49+
try {
50+
// First try to load the wallet using the encryption password
51+
await wallet.import({
52+
encryptedJson: config.authWalletEncryptedJson,
53+
password: env.ENCRYPTION_PASSWORD,
54+
});
55+
} catch {
56+
// If that fails, try the thirdweb api secret key for backwards compatibility
57+
await wallet.import({
58+
encryptedJson: config.authWalletEncryptedJson,
59+
password: env.THIRDWEB_API_SECRET_KEY,
60+
});
61+
62+
// If that works, save the wallet using the encryption password for the future
63+
const authWalletEncryptedJson = await wallet.export({
64+
strategy: "encryptedJson",
65+
password: env.ENCRYPTION_PASSWORD,
66+
});
67+
68+
await updateConfiguration({
69+
authWalletEncryptedJson,
70+
});
71+
}
5072

5173
const jwt = await buildJWT({
5274
wallet,

src/server/utils/wallets/createLocalWallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const createLocalWallet = async ({
2222
// Creating wallet details row is handled by LocalFileStorage
2323
await wallet.save({
2424
strategy: "encryptedJson",
25-
password: env.THIRDWEB_API_SECRET_KEY,
25+
password: env.ENCRYPTION_PASSWORD,
2626
storage: new LocalFileStorage(walletAddress, label),
2727
});
2828

src/server/utils/wallets/getLocalWallet.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { getChainByChainId } from "@thirdweb-dev/chains";
22
import { LocalWallet } from "@thirdweb-dev/wallets";
3+
import { getWalletDetails } from "../../../db/wallets/getWalletDetails";
34
import { env } from "../../../utils/env";
5+
import { logger } from "../../../utils/logger";
46
import { LocalFileStorage } from "../storage/localStorage";
57

68
interface GetLocalWalletParams {
@@ -14,11 +16,44 @@ export const getLocalWallet = async ({
1416
}: GetLocalWalletParams) => {
1517
const chain = getChainByChainId(chainId);
1618
const wallet = new LocalWallet({ chain });
17-
await wallet.load({
18-
strategy: "encryptedJson",
19-
password: env.THIRDWEB_API_SECRET_KEY,
20-
storage: new LocalFileStorage(walletAddress),
21-
});
19+
20+
// TODO: Remove this with next breaking change
21+
try {
22+
// First, try to load the wallet using the encryption password
23+
await wallet.load({
24+
strategy: "encryptedJson",
25+
password: env.ENCRYPTION_PASSWORD,
26+
storage: new LocalFileStorage(walletAddress),
27+
});
28+
} catch {
29+
// If that fails, try the thirdweb api secret key for backwards compatibility
30+
await wallet.load({
31+
strategy: "encryptedJson",
32+
password: env.THIRDWEB_API_SECRET_KEY,
33+
storage: new LocalFileStorage(walletAddress),
34+
});
35+
36+
// If that works, save the wallet using the encryption password for the future
37+
const walletDetails = await getWalletDetails({ address: walletAddress });
38+
if (!walletDetails) {
39+
throw new Error(
40+
`Wallet details not found for wallet address ${walletAddress}`,
41+
);
42+
}
43+
44+
logger.worker.info(
45+
`[Encryption] Updating local wallet ${walletAddress} to use ENCRYPTION_PASSWORD`,
46+
);
47+
48+
await wallet.save({
49+
strategy: "encryptedJson",
50+
password: env.ENCRYPTION_PASSWORD,
51+
storage: new LocalFileStorage(
52+
walletAddress,
53+
walletDetails.label ?? undefined,
54+
),
55+
});
56+
}
2257

2358
return wallet;
2459
};

src/server/utils/wallets/importLocalWallet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const importLocalWallet = async (
5151
// Creating wallet details gets handled by LocalFileStorage
5252
await wallet.save({
5353
strategy: "encryptedJson",
54-
password: env.THIRDWEB_API_SECRET_KEY,
54+
password: env.ENCRYPTION_PASSWORD,
5555
storage: new LocalFileStorage(walletAddress, options.label),
5656
});
5757

src/utils/crypto.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ import crypto from "crypto-js";
22
import { env } from "./env";
33

44
export const encrypt = (data: string): string => {
5-
return crypto.AES.encrypt(data, env.THIRDWEB_API_SECRET_KEY).toString();
5+
return crypto.AES.encrypt(data, env.ENCRYPTION_PASSWORD).toString();
66
};
77

8-
export const decrypt = (data: string) => {
9-
return crypto.AES.decrypt(data, env.THIRDWEB_API_SECRET_KEY).toString(
10-
crypto.enc.Utf8,
11-
);
8+
export const decrypt = (data: string, password: string) => {
9+
return crypto.AES.decrypt(data, password).toString(crypto.enc.Utf8);
1210
};

0 commit comments

Comments
 (0)