@kedaruma/revlm-client 1.0.10 → 1.0.13

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.
package/dist/index.d.mts CHANGED
@@ -173,6 +173,12 @@ type RevlmOptions = {
173
173
  provisionalAuthSecretMaster?: string;
174
174
  provisionalAuthDomain?: string;
175
175
  autoSetToken?: boolean;
176
+ autoRefreshOn401?: boolean;
177
+ sigv4SecretKey?: string;
178
+ sigv4AccessKey?: string;
179
+ sigv4Region?: string;
180
+ sigv4Service?: string;
181
+ sigv4Enabled?: boolean;
176
182
  };
177
183
  type RevlmResponse<T = any> = {
178
184
  ok: boolean;
@@ -191,6 +197,8 @@ declare class Revlm {
191
197
  private provisionalAuthSecretMaster;
192
198
  private provisionalAuthDomain;
193
199
  private autoSetToken;
200
+ private autoRefreshOn401;
201
+ private sigv4Signer;
194
202
  constructor(baseUrl: string, opts?: RevlmOptions);
195
203
  setToken(token: string): void;
196
204
  getToken(): string | undefined;
@@ -201,6 +209,9 @@ declare class Revlm {
201
209
  private makeHeaders;
202
210
  private parseResponse;
203
211
  private request;
212
+ private shouldSkipAuthRetry;
213
+ private signIfNeeded;
214
+ private requestWithRetry;
204
215
  login(authId: string, password: string): Promise<LoginResponse>;
205
216
  provisionalLogin(authId: string): Promise<ProvisionalLoginResponse>;
206
217
  registerUser(user: UserInput, password: string): Promise<RevlmResponse<any>>;
package/dist/index.d.ts CHANGED
@@ -173,6 +173,12 @@ type RevlmOptions = {
173
173
  provisionalAuthSecretMaster?: string;
174
174
  provisionalAuthDomain?: string;
175
175
  autoSetToken?: boolean;
176
+ autoRefreshOn401?: boolean;
177
+ sigv4SecretKey?: string;
178
+ sigv4AccessKey?: string;
179
+ sigv4Region?: string;
180
+ sigv4Service?: string;
181
+ sigv4Enabled?: boolean;
176
182
  };
177
183
  type RevlmResponse<T = any> = {
178
184
  ok: boolean;
@@ -191,6 +197,8 @@ declare class Revlm {
191
197
  private provisionalAuthSecretMaster;
192
198
  private provisionalAuthDomain;
193
199
  private autoSetToken;
200
+ private autoRefreshOn401;
201
+ private sigv4Signer;
194
202
  constructor(baseUrl: string, opts?: RevlmOptions);
195
203
  setToken(token: string): void;
196
204
  getToken(): string | undefined;
@@ -201,6 +209,9 @@ declare class Revlm {
201
209
  private makeHeaders;
202
210
  private parseResponse;
203
211
  private request;
212
+ private shouldSkipAuthRetry;
213
+ private signIfNeeded;
214
+ private requestWithRetry;
204
215
  login(authId: string, password: string): Promise<LoginResponse>;
205
216
  provisionalLogin(authId: string): Promise<ProvisionalLoginResponse>;
206
217
  registerUser(user: UserInput, password: string): Promise<RevlmResponse<any>>;
package/dist/index.js CHANGED
@@ -46,6 +46,8 @@ module.exports = __toCommonJS(index_exports);
46
46
  // src/Revlm.ts
47
47
  var import_bson = require("bson");
48
48
  var import_revlm_shared = require("@kedaruma/revlm-shared");
49
+ var import_signature_v4 = require("@aws-sdk/signature-v4");
50
+ var import_sha256_js = require("@aws-crypto/sha256-js");
49
51
 
50
52
  // src/MdbCollection.ts
51
53
  var MdbCollection = class {
@@ -155,6 +157,8 @@ var Revlm = class {
155
157
  provisionalAuthSecretMaster;
156
158
  provisionalAuthDomain;
157
159
  autoSetToken;
160
+ autoRefreshOn401;
161
+ sigv4Signer;
158
162
  constructor(baseUrl, opts = {}) {
159
163
  if (!baseUrl) throw new Error("baseUrl is required");
160
164
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -164,6 +168,25 @@ var Revlm = class {
164
168
  this.provisionalAuthSecretMaster = opts.provisionalAuthSecretMaster || "";
165
169
  this.provisionalAuthDomain = opts.provisionalAuthDomain || "";
166
170
  this.autoSetToken = opts.autoSetToken ?? true;
171
+ this.autoRefreshOn401 = opts.autoRefreshOn401 || false;
172
+ const sigv4SecretKey = opts.sigv4SecretKey || process.env.REVLM_SIGV4_SECRET_KEY;
173
+ const sigv4AccessKey = opts.sigv4AccessKey || process.env.REVLM_SIGV4_ACCESS_KEY || "revlm-access";
174
+ const sigv4Region = opts.sigv4Region || process.env.REVLM_SIGV4_REGION || "revlm";
175
+ const sigv4Service = opts.sigv4Service || process.env.REVLM_SIGV4_SERVICE || "revlm";
176
+ const sigv4Enabled = opts.sigv4Enabled ?? true;
177
+ if (sigv4Enabled) {
178
+ if (!sigv4SecretKey) {
179
+ throw new Error("SigV4 is enabled but REVLM_SIGV4_SECRET_KEY or opts.sigv4SecretKey is not provided");
180
+ }
181
+ this.sigv4Signer = new import_signature_v4.SignatureV4({
182
+ credentials: { accessKeyId: sigv4AccessKey, secretAccessKey: sigv4SecretKey },
183
+ region: sigv4Region,
184
+ service: sigv4Service,
185
+ sha256: import_sha256_js.Sha256
186
+ });
187
+ } else {
188
+ this.sigv4Signer = null;
189
+ }
167
190
  if (!this.fetchImpl) {
168
191
  throw new Error("No fetch implementation available. Provide fetchImpl in options or run in Node 18+ with global fetch.");
169
192
  }
@@ -185,7 +208,7 @@ var Revlm = class {
185
208
  // On success, if autoSetToken is true and res.token is set, update the client token.
186
209
  async refreshToken() {
187
210
  if (!this._token) return { ok: false, error: "No token set" };
188
- const res = await this.request("/refresh-token", "POST");
211
+ const res = await this.requestWithRetry("/refresh-token", "POST", void 0, { allowAuthRetry: false, retrying: false });
189
212
  if (this.autoSetToken && res && res.ok && res.token) {
190
213
  this.setToken(res.token);
191
214
  }
@@ -212,7 +235,7 @@ var Revlm = class {
212
235
  headers["Content-Type"] = "application/ejson";
213
236
  }
214
237
  if (this._token) {
215
- headers["Authorization"] = `Bearer ${this._token}`;
238
+ headers["X-Revlm-JWT"] = `Bearer ${this._token}`;
216
239
  }
217
240
  return headers;
218
241
  }
@@ -230,6 +253,41 @@ var Revlm = class {
230
253
  }
231
254
  }
232
255
  async request(path, method = "POST", body) {
256
+ return this.requestWithRetry(path, method, body, { allowAuthRetry: this.autoRefreshOn401, retrying: false });
257
+ }
258
+ shouldSkipAuthRetry(path) {
259
+ const pathname = path.startsWith("http") ? new URL(path).pathname : path;
260
+ return pathname.includes("/login") || pathname.includes("/provisional-login") || pathname.includes("/refresh-token") || pathname.includes("/verify-token");
261
+ }
262
+ async signIfNeeded(url, method, headers, body) {
263
+ if (!this.sigv4Signer) {
264
+ return { signedUrl: url, signedHeaders: headers };
265
+ }
266
+ const u = new URL(url);
267
+ const signingHeaders = {
268
+ host: u.host,
269
+ ...headers
270
+ };
271
+ const reqToSign = {
272
+ method,
273
+ protocol: u.protocol,
274
+ path: u.pathname + (u.search || ""),
275
+ hostname: u.hostname,
276
+ headers: signingHeaders,
277
+ body: body ?? ""
278
+ };
279
+ if (u.port) {
280
+ reqToSign.port = Number(u.port);
281
+ }
282
+ const signed = await this.sigv4Signer.sign(reqToSign);
283
+ const signedHeaders = {};
284
+ Object.entries(signed.headers || {}).forEach(([k, v]) => {
285
+ signedHeaders[k] = Array.isArray(v) ? v.join(",") : String(v);
286
+ });
287
+ return { signedUrl: url, signedHeaders };
288
+ }
289
+ async requestWithRetry(path, method = "POST", body, opts = { allowAuthRetry: false, retrying: false }) {
290
+ const { allowAuthRetry, retrying } = opts;
233
291
  const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
234
292
  const hasBody = body !== void 0;
235
293
  const headers = this.makeHeaders(hasBody);
@@ -237,10 +295,11 @@ var Revlm = class {
237
295
  if (hasBody) {
238
296
  serializedBody = import_bson.EJSON.stringify(body);
239
297
  }
298
+ const { signedUrl, signedHeaders } = await this.signIfNeeded(url, method, headers, serializedBody);
240
299
  try {
241
- const res = await this.fetchImpl(url, {
300
+ const res = await this.fetchImpl(signedUrl, {
242
301
  method,
243
- headers,
302
+ headers: signedHeaders,
244
303
  body: serializedBody
245
304
  });
246
305
  const parsed = await this.parseResponse(res);
@@ -249,6 +308,12 @@ var Revlm = class {
249
308
  if (out && out.ok === false && !out.error) {
250
309
  out.error = parsed?.reason || parsed?.message || "Unknown error";
251
310
  }
311
+ if (allowAuthRetry && !retrying && res.status === 401 && !this.shouldSkipAuthRetry(path)) {
312
+ const refreshRes = await this.refreshToken();
313
+ if (refreshRes && refreshRes.ok && refreshRes.token) {
314
+ return this.requestWithRetry(path, method, body, { allowAuthRetry: false, retrying: true });
315
+ }
316
+ }
252
317
  return out;
253
318
  } catch (err) {
254
319
  return { ok: false, error: err?.message || String(err) };
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/Revlm.ts
2
2
  import { EJSON } from "bson";
3
3
  import { AuthClient } from "@kedaruma/revlm-shared";
4
+ import { SignatureV4 } from "@aws-sdk/signature-v4";
5
+ import { Sha256 } from "@aws-crypto/sha256-js";
4
6
 
5
7
  // src/MdbCollection.ts
6
8
  var MdbCollection = class {
@@ -110,6 +112,8 @@ var Revlm = class {
110
112
  provisionalAuthSecretMaster;
111
113
  provisionalAuthDomain;
112
114
  autoSetToken;
115
+ autoRefreshOn401;
116
+ sigv4Signer;
113
117
  constructor(baseUrl, opts = {}) {
114
118
  if (!baseUrl) throw new Error("baseUrl is required");
115
119
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -119,6 +123,25 @@ var Revlm = class {
119
123
  this.provisionalAuthSecretMaster = opts.provisionalAuthSecretMaster || "";
120
124
  this.provisionalAuthDomain = opts.provisionalAuthDomain || "";
121
125
  this.autoSetToken = opts.autoSetToken ?? true;
126
+ this.autoRefreshOn401 = opts.autoRefreshOn401 || false;
127
+ const sigv4SecretKey = opts.sigv4SecretKey || process.env.REVLM_SIGV4_SECRET_KEY;
128
+ const sigv4AccessKey = opts.sigv4AccessKey || process.env.REVLM_SIGV4_ACCESS_KEY || "revlm-access";
129
+ const sigv4Region = opts.sigv4Region || process.env.REVLM_SIGV4_REGION || "revlm";
130
+ const sigv4Service = opts.sigv4Service || process.env.REVLM_SIGV4_SERVICE || "revlm";
131
+ const sigv4Enabled = opts.sigv4Enabled ?? true;
132
+ if (sigv4Enabled) {
133
+ if (!sigv4SecretKey) {
134
+ throw new Error("SigV4 is enabled but REVLM_SIGV4_SECRET_KEY or opts.sigv4SecretKey is not provided");
135
+ }
136
+ this.sigv4Signer = new SignatureV4({
137
+ credentials: { accessKeyId: sigv4AccessKey, secretAccessKey: sigv4SecretKey },
138
+ region: sigv4Region,
139
+ service: sigv4Service,
140
+ sha256: Sha256
141
+ });
142
+ } else {
143
+ this.sigv4Signer = null;
144
+ }
122
145
  if (!this.fetchImpl) {
123
146
  throw new Error("No fetch implementation available. Provide fetchImpl in options or run in Node 18+ with global fetch.");
124
147
  }
@@ -140,7 +163,7 @@ var Revlm = class {
140
163
  // On success, if autoSetToken is true and res.token is set, update the client token.
141
164
  async refreshToken() {
142
165
  if (!this._token) return { ok: false, error: "No token set" };
143
- const res = await this.request("/refresh-token", "POST");
166
+ const res = await this.requestWithRetry("/refresh-token", "POST", void 0, { allowAuthRetry: false, retrying: false });
144
167
  if (this.autoSetToken && res && res.ok && res.token) {
145
168
  this.setToken(res.token);
146
169
  }
@@ -167,7 +190,7 @@ var Revlm = class {
167
190
  headers["Content-Type"] = "application/ejson";
168
191
  }
169
192
  if (this._token) {
170
- headers["Authorization"] = `Bearer ${this._token}`;
193
+ headers["X-Revlm-JWT"] = `Bearer ${this._token}`;
171
194
  }
172
195
  return headers;
173
196
  }
@@ -185,6 +208,41 @@ var Revlm = class {
185
208
  }
186
209
  }
187
210
  async request(path, method = "POST", body) {
211
+ return this.requestWithRetry(path, method, body, { allowAuthRetry: this.autoRefreshOn401, retrying: false });
212
+ }
213
+ shouldSkipAuthRetry(path) {
214
+ const pathname = path.startsWith("http") ? new URL(path).pathname : path;
215
+ return pathname.includes("/login") || pathname.includes("/provisional-login") || pathname.includes("/refresh-token") || pathname.includes("/verify-token");
216
+ }
217
+ async signIfNeeded(url, method, headers, body) {
218
+ if (!this.sigv4Signer) {
219
+ return { signedUrl: url, signedHeaders: headers };
220
+ }
221
+ const u = new URL(url);
222
+ const signingHeaders = {
223
+ host: u.host,
224
+ ...headers
225
+ };
226
+ const reqToSign = {
227
+ method,
228
+ protocol: u.protocol,
229
+ path: u.pathname + (u.search || ""),
230
+ hostname: u.hostname,
231
+ headers: signingHeaders,
232
+ body: body ?? ""
233
+ };
234
+ if (u.port) {
235
+ reqToSign.port = Number(u.port);
236
+ }
237
+ const signed = await this.sigv4Signer.sign(reqToSign);
238
+ const signedHeaders = {};
239
+ Object.entries(signed.headers || {}).forEach(([k, v]) => {
240
+ signedHeaders[k] = Array.isArray(v) ? v.join(",") : String(v);
241
+ });
242
+ return { signedUrl: url, signedHeaders };
243
+ }
244
+ async requestWithRetry(path, method = "POST", body, opts = { allowAuthRetry: false, retrying: false }) {
245
+ const { allowAuthRetry, retrying } = opts;
188
246
  const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
189
247
  const hasBody = body !== void 0;
190
248
  const headers = this.makeHeaders(hasBody);
@@ -192,10 +250,11 @@ var Revlm = class {
192
250
  if (hasBody) {
193
251
  serializedBody = EJSON.stringify(body);
194
252
  }
253
+ const { signedUrl, signedHeaders } = await this.signIfNeeded(url, method, headers, serializedBody);
195
254
  try {
196
- const res = await this.fetchImpl(url, {
255
+ const res = await this.fetchImpl(signedUrl, {
197
256
  method,
198
- headers,
257
+ headers: signedHeaders,
199
258
  body: serializedBody
200
259
  });
201
260
  const parsed = await this.parseResponse(res);
@@ -204,6 +263,12 @@ var Revlm = class {
204
263
  if (out && out.ok === false && !out.error) {
205
264
  out.error = parsed?.reason || parsed?.message || "Unknown error";
206
265
  }
266
+ if (allowAuthRetry && !retrying && res.status === 401 && !this.shouldSkipAuthRetry(path)) {
267
+ const refreshRes = await this.refreshToken();
268
+ if (refreshRes && refreshRes.ok && refreshRes.token) {
269
+ return this.requestWithRetry(path, method, body, { allowAuthRetry: false, retrying: true });
270
+ }
271
+ }
207
272
  return out;
208
273
  } catch (err) {
209
274
  return { ok: false, error: err?.message || String(err) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kedaruma/revlm-client",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "private": false,
5
5
  "description": "TypeScript client SDK for talking to the Revlm server replacement for MongoDB Realm.",
6
6
  "keywords": [
@@ -36,9 +36,11 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
+ "@aws-sdk/signature-v4": "^3.374.0",
40
+ "@aws-crypto/sha256-js": "^5.2.0",
39
41
  "bson": "^6.10.4",
40
42
  "dotenv": "^17.2.3",
41
- "@kedaruma/revlm-shared": "1.0.3"
43
+ "@kedaruma/revlm-shared": "1.0.4"
42
44
  },
43
45
  "devDependencies": {
44
46
  "tsup": "^8.5.1"