@riocrypto/common-server 1.0.2195 → 1.0.2196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,8 +4,26 @@ declare class SecretManagerClient {
|
|
|
4
4
|
private env;
|
|
5
5
|
client: SecretManagerServiceClient;
|
|
6
6
|
projectId: string;
|
|
7
|
+
private secretCache;
|
|
8
|
+
private readonly POLL_INTERVAL_MS;
|
|
7
9
|
constructor(env: RioEnv);
|
|
10
|
+
/**
|
|
11
|
+
* Get a secret value from cache or fetch it from Secret Manager
|
|
12
|
+
* Once fetched, the secret will be cached and automatically refreshed every 15 seconds
|
|
13
|
+
*/
|
|
8
14
|
getSecretValue(secretId: string): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetch a secret value directly from Secret Manager without using the cache
|
|
17
|
+
*/
|
|
18
|
+
private fetchSecretValue;
|
|
19
|
+
/**
|
|
20
|
+
* Set up cache entry with polling for a secret
|
|
21
|
+
*/
|
|
22
|
+
private setupCacheWithPolling;
|
|
23
|
+
/**
|
|
24
|
+
* Clear the cache for a specific secret or all secrets
|
|
25
|
+
*/
|
|
26
|
+
clearCache(secretId?: string): void;
|
|
9
27
|
}
|
|
10
28
|
export declare const secretManagerClient: SecretManagerClient;
|
|
11
29
|
export {};
|
|
@@ -39,6 +39,8 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
class SecretManagerClient {
|
|
40
40
|
constructor(env) {
|
|
41
41
|
this.env = env;
|
|
42
|
+
this.secretCache = new Map();
|
|
43
|
+
this.POLL_INTERVAL_MS = 15 * 1000; // 15 seconds
|
|
42
44
|
const secretFilePath = "/etc/secrets/secret-manager/secret-manager-service-account-key.json";
|
|
43
45
|
const secretFileContents = fs.readFileSync(secretFilePath, "utf8");
|
|
44
46
|
const secretData = JSON.parse(secretFileContents);
|
|
@@ -53,7 +55,29 @@ class SecretManagerClient {
|
|
|
53
55
|
},
|
|
54
56
|
});
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Get a secret value from cache or fetch it from Secret Manager
|
|
60
|
+
* Once fetched, the secret will be cached and automatically refreshed every 15 seconds
|
|
61
|
+
*/
|
|
56
62
|
getSecretValue(secretId) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
// Check if the secret is already in the cache
|
|
65
|
+
const cachedSecret = this.secretCache.get(secretId);
|
|
66
|
+
if (cachedSecret) {
|
|
67
|
+
return cachedSecret.value;
|
|
68
|
+
}
|
|
69
|
+
// If not in cache, fetch it and set up polling
|
|
70
|
+
const value = yield this.fetchSecretValue(secretId);
|
|
71
|
+
if (value) {
|
|
72
|
+
this.setupCacheWithPolling(secretId, value);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Fetch a secret value directly from Secret Manager without using the cache
|
|
79
|
+
*/
|
|
80
|
+
fetchSecretValue(secretId) {
|
|
57
81
|
var _a, _b;
|
|
58
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
59
83
|
let file;
|
|
@@ -102,5 +126,65 @@ class SecretManagerClient {
|
|
|
102
126
|
return null;
|
|
103
127
|
});
|
|
104
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Set up cache entry with polling for a secret
|
|
131
|
+
*/
|
|
132
|
+
setupCacheWithPolling(secretId, initialValue) {
|
|
133
|
+
// Only set up polling in non-test environments
|
|
134
|
+
if (process.env.NODE_ENV === "test") {
|
|
135
|
+
// For test environments, just cache the value without polling
|
|
136
|
+
this.secretCache.set(secretId, {
|
|
137
|
+
value: initialValue,
|
|
138
|
+
pollingInterval: null,
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Set up polling interval to refresh the secret
|
|
143
|
+
const pollingInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
144
|
+
try {
|
|
145
|
+
const newValue = yield this.fetchSecretValue(secretId);
|
|
146
|
+
if (newValue) {
|
|
147
|
+
const currentCached = this.secretCache.get(secretId);
|
|
148
|
+
// Only update and log if the value has changed
|
|
149
|
+
if (currentCached && currentCached.value !== newValue) {
|
|
150
|
+
console.info(`Secret ${secretId} has been updated`);
|
|
151
|
+
this.secretCache.set(secretId, {
|
|
152
|
+
value: newValue,
|
|
153
|
+
pollingInterval: currentCached.pollingInterval,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error(`Error polling secret ${secretId}:`, error);
|
|
160
|
+
}
|
|
161
|
+
}), this.POLL_INTERVAL_MS);
|
|
162
|
+
// Store the initial value and polling interval
|
|
163
|
+
this.secretCache.set(secretId, {
|
|
164
|
+
value: initialValue,
|
|
165
|
+
pollingInterval,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Clear the cache for a specific secret or all secrets
|
|
170
|
+
*/
|
|
171
|
+
clearCache(secretId) {
|
|
172
|
+
if (secretId) {
|
|
173
|
+
const cached = this.secretCache.get(secretId);
|
|
174
|
+
if (cached && cached.pollingInterval) {
|
|
175
|
+
clearInterval(cached.pollingInterval);
|
|
176
|
+
}
|
|
177
|
+
this.secretCache.delete(secretId);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Clear all cached secrets
|
|
181
|
+
this.secretCache.forEach((cached) => {
|
|
182
|
+
if (cached.pollingInterval) {
|
|
183
|
+
clearInterval(cached.pollingInterval);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
this.secretCache.clear();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
105
189
|
}
|
|
106
190
|
exports.secretManagerClient = new SecretManagerClient(process.env.RIO_ENV || process.env.NEXT_PUBLIC_RIO_ENV);
|
|
@@ -20,148 +20,173 @@ const auth_1 = require("../models/auth");
|
|
|
20
20
|
const apiKey_1 = require("../services/apiKey");
|
|
21
21
|
const secret_manager_client_1 = require("../clients/secret-manager-client");
|
|
22
22
|
const admin_auth_1 = require("../models/admin-auth");
|
|
23
|
+
const logger_1 = __importDefault(require("../services/logger"));
|
|
23
24
|
const authorize = (req, res, next, mongoose, authorizationTypes) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
-
var _a, _b
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
req.validGenisisAdminKey = true;
|
|
33
|
-
}
|
|
25
|
+
var _a, _b;
|
|
26
|
+
// Start performance monitoring
|
|
27
|
+
const logger = logger_1.default.getLogger();
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
// Early return for public routes
|
|
30
|
+
if (authorizationTypes.includes(common_1.AuthorizationType.Public)) {
|
|
31
|
+
next();
|
|
32
|
+
return;
|
|
34
33
|
}
|
|
34
|
+
// Prepare promises for parallel execution
|
|
35
|
+
const promises = [];
|
|
36
|
+
// Check for cluster API key - only if needed
|
|
35
37
|
if (authorizationTypes.includes(common_1.AuthorizationType.Cluster)) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const apiKey = req.header("x-cluster-api-key");
|
|
39
|
+
if (apiKey) {
|
|
40
|
+
promises.push((() => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const CLUSTER_API_KEY = yield secret_manager_client_1.secretManagerClient.getSecretValue("CLUSTER_API_KEY");
|
|
42
|
+
if (!CLUSTER_API_KEY) {
|
|
43
|
+
throw new common_1.SecretManagerError();
|
|
44
|
+
}
|
|
45
|
+
if (apiKey === CLUSTER_API_KEY) {
|
|
46
|
+
req.validClusterApiKey = true;
|
|
47
|
+
}
|
|
48
|
+
}))());
|
|
40
49
|
}
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
}
|
|
51
|
+
// Check for genesis admin key - only if needed
|
|
52
|
+
if (authorizationTypes.includes(common_1.AuthorizationType.GenesisAdmin)) {
|
|
53
|
+
const apiKey = req.header("x-genesis-api-key");
|
|
54
|
+
if (apiKey) {
|
|
55
|
+
promises.push((() => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
const GENESIS_ADMIN_KEY = yield secret_manager_client_1.secretManagerClient.getSecretValue("GENESIS_ADMIN_KEY");
|
|
57
|
+
if (!GENESIS_ADMIN_KEY) {
|
|
58
|
+
throw new common_1.SecretManagerError();
|
|
59
|
+
}
|
|
60
|
+
if (apiKey === GENESIS_ADMIN_KEY) {
|
|
61
|
+
req.validGenisisAdminKey = true;
|
|
62
|
+
}
|
|
63
|
+
}))());
|
|
43
64
|
}
|
|
44
65
|
}
|
|
66
|
+
// Check for admin auth - only if needed
|
|
45
67
|
if (authorizationTypes.includes(common_1.AuthorizationType.AdminAuth)) {
|
|
46
68
|
const adminApiKey = req.header("x-admin-api-key");
|
|
47
|
-
const
|
|
48
|
-
if (adminApiKey) {
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const ADMIN_ACCESS_TOKEN_SECRET = yield secret_manager_client_1.secretManagerClient.getSecretValue("ADMIN_ACCESS_TOKEN_SECRET");
|
|
63
|
-
if (!ADMIN_ACCESS_TOKEN_SECRET) {
|
|
64
|
-
throw new Error("Unable to get ADMIN_ACCESS_TOKEN_SECRET");
|
|
69
|
+
const adminAccessToken = (_a = req.cookies) === null || _a === void 0 ? void 0 : _a.adminAccessToken;
|
|
70
|
+
if (adminApiKey || adminAccessToken) {
|
|
71
|
+
promises.push((() => __awaiter(void 0, void 0, void 0, function* () {
|
|
72
|
+
const AdminAuth = yield (0, admin_auth_1.buildAdminAuth)(mongoose);
|
|
73
|
+
if (adminApiKey) {
|
|
74
|
+
try {
|
|
75
|
+
const hashedAdminApiKey = yield apiKey_1.ApiKey.toHash(adminApiKey);
|
|
76
|
+
const adminAuth = yield AdminAuth.findOne({
|
|
77
|
+
"apiKeys.value": hashedAdminApiKey,
|
|
78
|
+
});
|
|
79
|
+
if (adminAuth) {
|
|
80
|
+
req.adminAuth = adminAuth;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (err) { }
|
|
65
84
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
else if (adminAccessToken) {
|
|
86
|
+
try {
|
|
87
|
+
const ADMIN_ACCESS_TOKEN_SECRET = yield secret_manager_client_1.secretManagerClient.getSecretValue("ADMIN_ACCESS_TOKEN_SECRET");
|
|
88
|
+
if (!ADMIN_ACCESS_TOKEN_SECRET) {
|
|
89
|
+
throw new Error("Unable to get ADMIN_ACCESS_TOKEN_SECRET");
|
|
90
|
+
}
|
|
91
|
+
const payload = jsonwebtoken_1.default.verify(adminAccessToken, ADMIN_ACCESS_TOKEN_SECRET);
|
|
92
|
+
const adminAuth = yield AdminAuth.findById(payload.id);
|
|
93
|
+
if (adminAuth) {
|
|
94
|
+
req.adminAuth = adminAuth;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) { }
|
|
70
98
|
}
|
|
71
|
-
}
|
|
72
|
-
catch (err) { }
|
|
99
|
+
}))());
|
|
73
100
|
}
|
|
74
101
|
}
|
|
75
|
-
if
|
|
102
|
+
// Check for user auth - only if needed
|
|
103
|
+
const needsUserOrAuth = authorizationTypes.includes(common_1.AuthorizationType.Auth) ||
|
|
76
104
|
authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC) ||
|
|
77
|
-
authorizationTypes.includes(common_1.AuthorizationType.User)
|
|
105
|
+
authorizationTypes.includes(common_1.AuthorizationType.User);
|
|
106
|
+
if (needsUserOrAuth) {
|
|
78
107
|
const apiKey = req.header("x-api-key");
|
|
79
|
-
const
|
|
80
|
-
if (apiKey) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
const payload = jsonwebtoken_1.default.verify(req.cookies.accessToken, ACCESS_TOKEN_SECRET);
|
|
99
|
-
const auth = yield Auth.findById(payload.id);
|
|
100
|
-
if (auth) {
|
|
101
|
-
req.auth = auth;
|
|
108
|
+
const accessToken = (_b = req.cookies) === null || _b === void 0 ? void 0 : _b.accessToken;
|
|
109
|
+
if (apiKey || accessToken) {
|
|
110
|
+
promises.push((() => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
|
+
var _c, _d, _e, _f, _g, _h;
|
|
112
|
+
const Auth = yield (0, auth_1.buildAuth)(mongoose);
|
|
113
|
+
let authId = null;
|
|
114
|
+
if (apiKey) {
|
|
115
|
+
try {
|
|
116
|
+
const hashedApiKey = yield apiKey_1.ApiKey.toHash(apiKey);
|
|
117
|
+
const auth = yield Auth.findOne({
|
|
118
|
+
"apiKeys.value": hashedApiKey,
|
|
119
|
+
});
|
|
120
|
+
if (auth) {
|
|
121
|
+
req.auth = auth;
|
|
122
|
+
authId = auth.id;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (err) { }
|
|
102
126
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (user) {
|
|
116
|
-
if (authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC)) {
|
|
117
|
-
req.user = user;
|
|
127
|
+
else if (accessToken) {
|
|
128
|
+
try {
|
|
129
|
+
const ACCESS_TOKEN_SECRET = yield secret_manager_client_1.secretManagerClient.getSecretValue("ACCESS_TOKEN_SECRET");
|
|
130
|
+
if (!ACCESS_TOKEN_SECRET) {
|
|
131
|
+
throw new common_1.SecretManagerError();
|
|
132
|
+
}
|
|
133
|
+
const payload = jsonwebtoken_1.default.verify(accessToken, ACCESS_TOKEN_SECRET);
|
|
134
|
+
const auth = yield Auth.findById(payload.id);
|
|
135
|
+
if (auth) {
|
|
136
|
+
req.auth = auth;
|
|
137
|
+
authId = auth.id;
|
|
138
|
+
}
|
|
118
139
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
140
|
+
catch (err) { }
|
|
141
|
+
}
|
|
142
|
+
// Only fetch user if we need it and we have an auth ID
|
|
143
|
+
const needsUser = authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC) ||
|
|
144
|
+
authorizationTypes.includes(common_1.AuthorizationType.User);
|
|
145
|
+
if (needsUser && authId) {
|
|
146
|
+
try {
|
|
147
|
+
const User = yield (0, user_1.buildUser)(mongoose);
|
|
148
|
+
const user = yield User.findOne({
|
|
149
|
+
authIds: { $in: [authId] },
|
|
150
|
+
});
|
|
151
|
+
if (user) {
|
|
152
|
+
if (authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC)) {
|
|
153
|
+
req.user = user;
|
|
154
|
+
}
|
|
155
|
+
else if (authorizationTypes.includes(common_1.AuthorizationType.User)) {
|
|
156
|
+
if (((_d = (_c = user.onboarding) === null || _c === void 0 ? void 0 : _c.MX) === null || _d === void 0 ? void 0 : _d.status) === common_1.OnboardingStatus.Approved ||
|
|
157
|
+
((_f = (_e = user.onboarding) === null || _e === void 0 ? void 0 : _e.PE) === null || _f === void 0 ? void 0 : _f.status) === common_1.OnboardingStatus.Approved ||
|
|
158
|
+
((_h = (_g = user.onboarding) === null || _g === void 0 ? void 0 : _g.US) === null || _h === void 0 ? void 0 : _h.status) === common_1.OnboardingStatus.Approved) {
|
|
159
|
+
req.user = user;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
124
162
|
}
|
|
125
163
|
}
|
|
164
|
+
catch (err) { }
|
|
126
165
|
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) { }
|
|
166
|
+
}))());
|
|
129
167
|
}
|
|
130
168
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
next();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (authorizationTypes.includes(common_1.AuthorizationType.AdminAuth) &&
|
|
150
|
-
req.adminAuth) {
|
|
151
|
-
next();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if ((authorizationTypes.includes(common_1.AuthorizationType.User) ||
|
|
155
|
-
authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC)) &&
|
|
156
|
-
req.user) {
|
|
157
|
-
next();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
if (authorizationTypes.includes(common_1.AuthorizationType.AdminAuth) &&
|
|
161
|
-
req.adminAuth) {
|
|
169
|
+
// Wait for all promises to complete
|
|
170
|
+
yield Promise.all(promises);
|
|
171
|
+
// Check authorization results and proceed if authorized
|
|
172
|
+
if ((authorizationTypes.includes(common_1.AuthorizationType.Cluster) &&
|
|
173
|
+
req.validClusterApiKey) ||
|
|
174
|
+
(authorizationTypes.includes(common_1.AuthorizationType.GenesisAdmin) &&
|
|
175
|
+
req.validGenisisAdminKey) ||
|
|
176
|
+
(authorizationTypes.includes(common_1.AuthorizationType.Auth) && req.auth) ||
|
|
177
|
+
(authorizationTypes.includes(common_1.AuthorizationType.AdminAuth) &&
|
|
178
|
+
req.adminAuth) ||
|
|
179
|
+
((authorizationTypes.includes(common_1.AuthorizationType.User) ||
|
|
180
|
+
authorizationTypes.includes(common_1.AuthorizationType.UserNoKYC)) &&
|
|
181
|
+
req.user)) {
|
|
182
|
+
const endTime = Date.now();
|
|
183
|
+
logger.info(`Authorization completed in ${endTime - startTime}ms`);
|
|
162
184
|
next();
|
|
163
185
|
return;
|
|
164
186
|
}
|
|
187
|
+
// Log unauthorized attempt
|
|
188
|
+
const endTime = Date.now();
|
|
189
|
+
logger.info(`Authorization failed in ${endTime - startTime}ms`);
|
|
165
190
|
throw new common_1.NotAuthorizedError();
|
|
166
191
|
});
|
|
167
192
|
exports.authorize = authorize;
|