@assinafy/sdk 2.2.0 → 2.4.0

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.js CHANGED
@@ -41,6 +41,8 @@ __export(index_exports, {
41
41
  MAX_LIST_PAGE_SIZE: () => MAX_LIST_PAGE_SIZE,
42
42
  MAX_UPLOAD_BYTES: () => MAX_UPLOAD_BYTES,
43
43
  NetworkError: () => NetworkError,
44
+ OAuthError: () => OAuthError,
45
+ OAuthResource: () => OAuthResource,
44
46
  SDK_USER_AGENT: () => SDK_USER_AGENT,
45
47
  SignerDocumentsResource: () => SignerDocumentsResource,
46
48
  SignerResource: () => SignerResource,
@@ -51,7 +53,8 @@ __export(index_exports, {
51
53
  WebhookResource: () => WebhookResource,
52
54
  WebhookVerifier: () => WebhookVerifier,
53
55
  WorkspaceResource: () => WorkspaceResource,
54
- buildAssignmentPayload: () => buildAssignmentPayload
56
+ buildAssignmentPayload: () => buildAssignmentPayload,
57
+ parseWwwAuthenticate: () => parseWwwAuthenticate
55
58
  });
56
59
  module.exports = __toCommonJS(index_exports);
57
60
 
@@ -89,6 +92,29 @@ var AssinafyError = class extends Error {
89
92
  var ApiError = class _ApiError extends AssinafyError {
90
93
  statusCode;
91
94
  responseData;
95
+ /**
96
+ * Parsed `WWW-Authenticate` challenge, when the response carried one.
97
+ *
98
+ * A `403` whose challenge is `{ error: 'insufficient_scope', scope: '…' }`
99
+ * means the OAuth token is valid but was never granted that permission:
100
+ * send the user through the authorization flow again asking for the scope
101
+ * named in `scope`. A `403` without a challenge has a different cause —
102
+ * another workspace, the user's role, or a surface OAuth tokens never
103
+ * reach — and reconnecting will not fix it.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * try {
108
+ * await connected.documents.upload({ filePath: './contract.pdf' });
109
+ * } catch (error) {
110
+ * if (error instanceof ApiError && error.challenge?.error === 'insufficient_scope') {
111
+ * return reconnect(error.challenge.scope); // 'documents:write'
112
+ * }
113
+ * throw error;
114
+ * }
115
+ * ```
116
+ */
117
+ challenge;
92
118
  /**
93
119
  * Create an error representing a non-success API response.
94
120
  *
@@ -134,6 +160,55 @@ var ApiError = class _ApiError extends AssinafyError {
134
160
  return new _ApiError(message, statusCode, responseData);
135
161
  }
136
162
  };
163
+ var OAuthError = class _OAuthError extends ApiError {
164
+ /** RFC 6749 error code, e.g. `invalid_grant`. */
165
+ error;
166
+ /** The server's human-readable explanation, when it sent one. */
167
+ errorDescription;
168
+ /**
169
+ * Create an OAuth protocol error.
170
+ *
171
+ * @param error - RFC 6749 error code.
172
+ * @param errorDescription - Server-provided explanation, or `null`.
173
+ * @param statusCode - HTTP status that carried it. Authorization responses
174
+ * arrive as redirect query parameters rather than an HTTP response, so
175
+ * {@link OAuthResource.readAuthorizationCallback} reports them as `400`.
176
+ * @param responseData - The raw error object.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * throw new OAuthError('invalid_grant', 'Authorization code expired.', 400);
181
+ * ```
182
+ */
183
+ constructor(error, errorDescription = null, statusCode = 400, responseData = null) {
184
+ super(errorDescription ? `${error}: ${errorDescription}` : error, statusCode, responseData);
185
+ this.name = "OAuthError";
186
+ this.error = error;
187
+ this.errorDescription = errorDescription;
188
+ }
189
+ /**
190
+ * Upgrade an {@link ApiError} to an {@link OAuthError} when its body is an
191
+ * RFC 6749 error object; otherwise return the value untouched.
192
+ *
193
+ * @param error - Any thrown value.
194
+ * @returns An `OAuthError` when the body carries a non-empty string
195
+ * `error`, else the original value.
196
+ */
197
+ static upgrade(error) {
198
+ if (!(error instanceof ApiError) || error instanceof _OAuthError) return error;
199
+ const body = error.responseData;
200
+ if (body === null || typeof body !== "object") return error;
201
+ const code = body["error"];
202
+ if (typeof code !== "string" || code.length === 0) return error;
203
+ const description = body["error_description"];
204
+ return new _OAuthError(
205
+ code,
206
+ typeof description === "string" && description.length > 0 ? description : null,
207
+ error.statusCode,
208
+ body
209
+ );
210
+ }
211
+ };
137
212
  var ValidationError = class extends AssinafyError {
138
213
  errors;
139
214
  /**
@@ -173,6 +248,43 @@ var NetworkError = class extends AssinafyError {
173
248
 
174
249
  // src/utils.ts
175
250
  var import_axios = __toESM(require("axios"));
251
+
252
+ // src/support/headers.ts
253
+ function readHeader(headers, name) {
254
+ if (!headers) return void 0;
255
+ const lower = name.toLowerCase();
256
+ for (const [key, value] of Object.entries(headers)) {
257
+ if (key.toLowerCase() === lower && value != null) {
258
+ const first = Array.isArray(value) ? value[0] : value;
259
+ if (typeof first === "string" || typeof first === "number" || typeof first === "boolean") {
260
+ return String(first);
261
+ }
262
+ return void 0;
263
+ }
264
+ }
265
+ return void 0;
266
+ }
267
+ var CHALLENGE_PARAM = /([A-Za-z0-9_-]+)\s*=\s*(?:"([^"]*)"|([^\s,]+))/gu;
268
+ function parseWwwAuthenticate(value) {
269
+ if (typeof value !== "string") return void 0;
270
+ const trimmed = value.trim();
271
+ const scheme = /^[A-Za-z0-9_-]+/u.exec(trimmed)?.[0];
272
+ if (!scheme) return void 0;
273
+ const challenge = { scheme };
274
+ CHALLENGE_PARAM.lastIndex = 0;
275
+ for (const match of trimmed.slice(scheme.length).matchAll(CHALLENGE_PARAM)) {
276
+ const key = match[1]?.toLowerCase();
277
+ const paramValue = match[2] ?? match[3];
278
+ if (paramValue === void 0) continue;
279
+ if (key === "error") challenge.error = paramValue;
280
+ else if (key === "error_description") challenge.error_description = paramValue;
281
+ else if (key === "scope") challenge.scope = paramValue;
282
+ else if (key === "resource_metadata") challenge.resource_metadata = paramValue;
283
+ }
284
+ return challenge;
285
+ }
286
+
287
+ // src/utils.ts
176
288
  var SAFE_LOG_NUMBER_FIELDS = /* @__PURE__ */ new Set([
177
289
  "attempt",
178
290
  "attempts",
@@ -216,7 +328,12 @@ function toSdkError(error, fallbackMessage) {
216
328
  const status = error.response?.status;
217
329
  if (status) {
218
330
  const body = decodeBinaryErrorBody(error.response?.data ?? null);
219
- return ApiError.fromResponse(status, body ?? null);
331
+ const apiError = ApiError.fromResponse(status, body ?? null);
332
+ const challenge = parseWwwAuthenticate(
333
+ readHeader(error.response?.headers, "www-authenticate")
334
+ );
335
+ if (challenge) apiError.challenge = challenge;
336
+ return apiError;
220
337
  }
221
338
  const cause = sanitiseNetworkCause(error);
222
339
  return new NetworkError(`${fallbackMessage}: ${cause.message}`, { cause });
@@ -366,22 +483,6 @@ function cleanListParams(params) {
366
483
  return out;
367
484
  }
368
485
 
369
- // src/support/headers.ts
370
- function readHeader(headers, name) {
371
- if (!headers) return void 0;
372
- const lower = name.toLowerCase();
373
- for (const [key, value] of Object.entries(headers)) {
374
- if (key.toLowerCase() === lower && value != null) {
375
- const first = Array.isArray(value) ? value[0] : value;
376
- if (typeof first === "string" || typeof first === "number" || typeof first === "boolean") {
377
- return String(first);
378
- }
379
- return void 0;
380
- }
381
- }
382
- return void 0;
383
- }
384
-
385
486
  // src/support/retry.ts
386
487
  function retryDelayFromHeaders(headers) {
387
488
  const retryAfter = readHeader(headers, "retry-after");
@@ -533,7 +634,7 @@ var import_axios2 = __toESM(require("axios"));
533
634
  // package.json
534
635
  var package_default = {
535
636
  name: "@assinafy/sdk",
536
- version: "2.2.0",
637
+ version: "2.4.0",
537
638
  packageManager: "bun@1.4.0",
538
639
  description: "TypeScript SDK for Assinafy API - Digital signature platform",
539
640
  type: "commonjs",
@@ -620,6 +721,7 @@ var package_default = {
620
721
  "dist",
621
722
  "docs",
622
723
  "README.md",
724
+ "README.en.md",
623
725
  "CHANGELOG.md",
624
726
  "SECURITY.md",
625
727
  "LICENSE"
@@ -871,14 +973,34 @@ function toInt(value) {
871
973
  var ASSIGNMENT_METHODS = /* @__PURE__ */ new Set(["virtual", "collect"]);
872
974
  var VERIFICATION_METHODS = /* @__PURE__ */ new Set(["Email", "Whatsapp", "DigitalCertificate"]);
873
975
  var NOTIFICATION_METHODS = /* @__PURE__ */ new Set(["Email", "Whatsapp"]);
976
+ var ALLOWED_NOTIFICATION_METHODS = {
977
+ Email: /* @__PURE__ */ new Set(["Email"]),
978
+ Whatsapp: /* @__PURE__ */ new Set(["Whatsapp"]),
979
+ DigitalCertificate: NOTIFICATION_METHODS
980
+ };
874
981
  function validateAssignmentSignerOptions(signer, label = "signer") {
875
982
  if (signer.verification_method !== void 0 && (typeof signer.verification_method !== "string" || !VERIFICATION_METHODS.has(signer.verification_method))) {
876
983
  throw new ValidationError(`${label} has an invalid verification_method`);
877
984
  }
878
- if (signer.notification_methods !== void 0 && (!Array.isArray(signer.notification_methods) || signer.notification_methods.some(
879
- (method) => typeof method !== "string" || !NOTIFICATION_METHODS.has(method)
880
- ))) {
881
- throw new ValidationError(`${label} has invalid notification_methods`);
985
+ if (signer.notification_methods !== void 0) {
986
+ const methods = signer.notification_methods;
987
+ if (!Array.isArray(methods) || methods.some(
988
+ (method) => typeof method !== "string" || !NOTIFICATION_METHODS.has(method)
989
+ )) {
990
+ throw new ValidationError(`${label} has invalid notification_methods`);
991
+ }
992
+ if (methods.length !== 1) {
993
+ throw new ValidationError(`${label} allows exactly one notification method`);
994
+ }
995
+ const verification = signer.verification_method;
996
+ if (typeof verification === "string") {
997
+ const allowed = ALLOWED_NOTIFICATION_METHODS[verification];
998
+ if (allowed && !allowed.has(methods[0])) {
999
+ throw new ValidationError(
1000
+ `${label} cannot pair ${verification} verification with ${String(methods[0])} notification`
1001
+ );
1002
+ }
1003
+ }
882
1004
  }
883
1005
  if (signer.step !== void 0 && (typeof signer.step !== "number" || !Number.isSafeInteger(signer.step) || signer.step < 1)) {
884
1006
  throw new ValidationError(`${label} step must be a positive safe integer`);
@@ -949,8 +1071,8 @@ function buildAssignmentEstimatePayload(payload) {
949
1071
  throw new ValidationError("method must be virtual or collect");
950
1072
  }
951
1073
  const signers = Array.isArray(payload.signers) ? payload.signers : [];
952
- if (method === "virtual" && signers.length === 0) {
953
- throw new ValidationError("At least one signer is required for a virtual cost estimate");
1074
+ if (signers.length === 0) {
1075
+ throw new ValidationError("At least one signer is required for a cost estimate");
954
1076
  }
955
1077
  const entries = payload.entries === void 0 ? void 0 : normaliseAssignmentEntries(payload.entries);
956
1078
  if (method === "collect" && (!entries || entries.length === 0)) {
@@ -958,7 +1080,7 @@ function buildAssignmentEstimatePayload(payload) {
958
1080
  }
959
1081
  return cleanParams({
960
1082
  method,
961
- signers: signers.length > 0 ? signers.map((signer) => normaliseEstimateSigner(signer)) : void 0,
1083
+ signers: signers.map((signer) => normaliseEstimateSigner(signer)),
962
1084
  entries
963
1085
  });
964
1086
  }
@@ -1339,7 +1461,8 @@ var AssignmentResource = class extends BaseResource {
1339
1461
  * }
1340
1462
  * ```
1341
1463
  * @throws {ValidationError} If `documentId` is missing, a `virtual` request
1342
- * has no signer entry, or a `collect` request has no field-placement entry.
1464
+ * has no signer entry, or a `collect` request has no signer entry or no
1465
+ * field-placement entry.
1343
1466
  * @throws {ApiError} If the API rejects the request.
1344
1467
  *
1345
1468
  * @example
@@ -2672,9 +2795,6 @@ function normaliseTemplateSigners(signers) {
2672
2795
  throw new ValidationError(`Template signer ${index + 1} requires id`);
2673
2796
  }
2674
2797
  validateAssignmentSignerOptions(signer, `Template signer ${index + 1}`);
2675
- if (signer.notification_methods !== void 0 && signer.notification_methods.length > 1) {
2676
- throw new ValidationError(`Template signer ${index + 1} allows one notification method`);
2677
- }
2678
2798
  const projected = { role_id: signer.role_id, id: signer.id };
2679
2799
  if (signer.verification_method !== void 0) {
2680
2800
  projected.verification_method = signer.verification_method;
@@ -4837,6 +4957,686 @@ var AuthenticationResource = class extends BaseResource {
4837
4957
  }
4838
4958
  };
4839
4959
 
4960
+ // src/resources/oauth.ts
4961
+ var import_node_crypto = require("crypto");
4962
+ var PROTECTED_RESOURCE_PATH = "/.well-known/oauth-protected-resource";
4963
+ var AUTHORIZATION_SERVER_PATH = "/.well-known/oauth-authorization-server";
4964
+ var CODE_VERIFIER_PATTERN = /^[A-Za-z0-9\-._~]{43,128}$/u;
4965
+ var OAuthResource = class extends BaseResource {
4966
+ publicHttp;
4967
+ constructor(http, defaultAccountId, logger, publicHttp) {
4968
+ super(http, defaultAccountId, logger);
4969
+ this.publicHttp = withoutCredentials(publicHttp ?? http);
4970
+ }
4971
+ /**
4972
+ * Read this API's protected-resource metadata
4973
+ * (`GET /.well-known/oauth-protected-resource`).
4974
+ *
4975
+ * Served at the API host root — not under `/v1` — and bare, without the
4976
+ * `{ status, message, data }` envelope, as RFC 8615 requires. Use it to
4977
+ * discover which authorization server may issue tokens for this API and
4978
+ * which scopes it accepts.
4979
+ *
4980
+ * Request body: none. Authentication: none.
4981
+ *
4982
+ * @returns The metadata document:
4983
+ * ```jsonc
4984
+ * {
4985
+ * "resource": "https://api.assinafy.com.br",
4986
+ * "authorization_servers": ["https://auth.assinafy.com.br"],
4987
+ * "scopes_supported": [
4988
+ * "documents:read", "documents:write",
4989
+ * "templates:read", "templates:write",
4990
+ * "account:read", "openid", "profile", "email"
4991
+ * ],
4992
+ * "bearer_methods_supported": ["header"]
4993
+ * }
4994
+ * ```
4995
+ * `offline_access` is deliberately absent: it is a request-time signal to
4996
+ * the authorization server, not a permission this API enforces.
4997
+ * @throws {ApiError} If the host does not publish the document.
4998
+ *
4999
+ * @example
5000
+ * ```ts
5001
+ * const metadata = await client.oauth.getProtectedResourceMetadata();
5002
+ * console.log(metadata.authorization_servers[0]);
5003
+ * ```
5004
+ */
5005
+ async getProtectedResourceMetadata() {
5006
+ return this.call(
5007
+ "Failed to fetch OAuth protected-resource metadata",
5008
+ () => this.publicHttp.get(`${this.apiOrigin()}${PROTECTED_RESOURCE_PATH}`)
5009
+ );
5010
+ }
5011
+ /**
5012
+ * Read the authorization server's metadata
5013
+ * (`GET {issuer}/.well-known/oauth-authorization-server`, RFC 8414).
5014
+ *
5015
+ * Every endpoint URL an OAuth client needs comes from here, so nothing has
5016
+ * to be hardcoded. The document is served by the authorization server, a
5017
+ * different host from this API.
5018
+ *
5019
+ * @param issuer - Issuer to read. Defaults to the first entry of
5020
+ * {@link OAuthResource.getProtectedResourceMetadata}, which costs one extra
5021
+ * request — pass the issuer to skip it.
5022
+ * @returns The metadata document:
5023
+ * ```jsonc
5024
+ * {
5025
+ * "issuer": "https://auth.assinafy.com.br",
5026
+ * "authorization_endpoint": "https://auth.assinafy.com.br/oauth/authorize",
5027
+ * "token_endpoint": "https://api.assinafy.com.br/v1/oauth/token",
5028
+ * "revocation_endpoint": "https://api.assinafy.com.br/v1/oauth/revoke",
5029
+ * "userinfo_endpoint": "https://api.assinafy.com.br/v1/oauth/userinfo",
5030
+ * "jwks_uri": "https://auth.assinafy.com.br/.well-known/jwks.json",
5031
+ * "scopes_supported": ["documents:read", "documents:write", "templates:read",
5032
+ * "templates:write", "account:read", "openid",
5033
+ * "profile", "email", "offline_access"],
5034
+ * "response_types_supported": ["code"],
5035
+ * "grant_types_supported": ["authorization_code", "refresh_token"],
5036
+ * "code_challenge_methods_supported": ["S256"],
5037
+ * "token_endpoint_auth_methods_supported": ["client_secret_post", "none"],
5038
+ * "authorization_response_iss_parameter_supported": true,
5039
+ * "client_id_metadata_document_supported": true
5040
+ * }
5041
+ * ```
5042
+ * @throws {ValidationError} If `issuer` is not an absolute `https://` URL,
5043
+ * or the document's own `issuer` disagrees with where it was fetched from
5044
+ * (RFC 8414 §3.3 — a mismatch means the document is not authoritative).
5045
+ * @throws {ApiError} If the authorization server rejects the request.
5046
+ *
5047
+ * @example
5048
+ * ```ts
5049
+ * const as = await client.oauth.getAuthorizationServerMetadata();
5050
+ * console.log(as.authorization_endpoint);
5051
+ * ```
5052
+ */
5053
+ async getAuthorizationServerMetadata(issuer) {
5054
+ const resolved = issuer ?? await this.defaultIssuer();
5055
+ const base = assertHttpsUrl(resolved, "issuer").replace(/\/+$/u, "");
5056
+ const metadata = await this.call(
5057
+ "Failed to fetch OAuth authorization-server metadata",
5058
+ () => this.publicHttp.get(`${base}${AUTHORIZATION_SERVER_PATH}`)
5059
+ );
5060
+ if (normaliseIssuer(metadata?.issuer) !== normaliseIssuer(base)) {
5061
+ throw new ValidationError(
5062
+ "Authorization-server metadata issuer does not match the requested issuer",
5063
+ { expected: base, received: metadata?.issuer ?? null }
5064
+ );
5065
+ }
5066
+ return metadata;
5067
+ }
5068
+ /**
5069
+ * Mint a PKCE pair and a `state`, then build the consent URL to send the
5070
+ * user's browser to (`GET {authorization_endpoint}`).
5071
+ *
5072
+ * Call this once per connection attempt and keep the whole returned object
5073
+ * in the user's session: reusing a verifier or a `state` across attempts
5074
+ * defeats both PKCE and CSRF protection. Navigate the browser to `url` with
5075
+ * a full page load — an `fetch`/XHR cannot show a consent screen.
5076
+ *
5077
+ * PKCE is mandatory for confidential applications too, and Assinafy accepts
5078
+ * only the `S256` challenge method.
5079
+ *
5080
+ * @param options - Authorization-request options.
5081
+ * @param options.clientId - The application's `client_id`.
5082
+ * @param options.redirectUri - One of the application's registered redirect
5083
+ * URIs, matched character for character (`…/callback` and `…/callback/` are
5084
+ * different). Must be `https://` and carry no fragment.
5085
+ * @param options.scopes - Permissions to request, e.g.
5086
+ * `['documents:read', 'documents:write', 'offline_access']`. Ask for the
5087
+ * minimum: the user approves all of them or none. Add `offline_access` to
5088
+ * receive a refresh token and `openid` to receive an `id_token`.
5089
+ * @param options.authorizationEndpoint - Skip discovery by supplying the
5090
+ * endpoint yourself. Defaults to the discovered
5091
+ * `authorization_endpoint`.
5092
+ * @param options.issuer - Issuer to discover from, and the value the
5093
+ * callback's `iss` must equal. Defaults to the discovered issuer.
5094
+ * @param options.resource - RFC 8707 resource indicator. Defaults to this
5095
+ * API's origin; pass `null` to omit it. It must match the value sent to the
5096
+ * token endpoint, or the exchange fails with `invalid_target`.
5097
+ * @param options.state - Supply your own CSRF value instead of a generated
5098
+ * one. Must be unique per attempt.
5099
+ * @param options.codeVerifier - Supply your own RFC 7636 verifier (43–128
5100
+ * characters from `A-Z a-z 0-9 - . _ ~`) instead of a generated one.
5101
+ * @param options.nonce - OIDC nonce echoed in the `id_token`. Generated
5102
+ * automatically when `openid` is requested; pass a string to set it or
5103
+ * `null` to omit it.
5104
+ * @param options.prompt - Forwarded as the OIDC `prompt` parameter, e.g.
5105
+ * `'consent'` to force the approval screen again.
5106
+ * @returns The request to store and redirect with:
5107
+ * ```jsonc
5108
+ * {
5109
+ * "url": "https://auth.assinafy.com.br/oauth/authorize?response_type=code&client_id=…&redirect_uri=https%3A%2F%2Fmyapp.com%2Foauth%2Fcallback&scope=documents%3Aread+offline_access&state=8Xv…&code_challenge=E9M…&code_challenge_method=S256&resource=https%3A%2F%2Fapi.assinafy.com.br",
5110
+ * "state": "8Xv2rQ7mJt0aLpKcWn4dZg",
5111
+ * "codeVerifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
5112
+ * "issuer": "https://auth.assinafy.com.br",
5113
+ * "nonce": "n-0S6_WzA2Mj"
5114
+ * }
5115
+ * ```
5116
+ * @throws {ValidationError} If `clientId` is empty, `redirectUri` is not an
5117
+ * absolute `https://` URL without a fragment, `scopes` is empty or contains
5118
+ * a value with whitespace, or a supplied `codeVerifier`/`state` is invalid.
5119
+ * @throws {ApiError} If discovery is needed and fails.
5120
+ *
5121
+ * @example
5122
+ * ```ts
5123
+ * const request = await client.oauth.createAuthorizationUrl({
5124
+ * clientId: process.env.ASSINAFY_CLIENT_ID!,
5125
+ * redirectUri: 'https://myapp.com/oauth/callback',
5126
+ * scopes: ['documents:read', 'documents:write', 'offline_access'],
5127
+ * });
5128
+ * session.oauth = request;
5129
+ * response.redirect(request.url);
5130
+ * ```
5131
+ */
5132
+ async createAuthorizationUrl(options) {
5133
+ assertRecord(options, "authorization options");
5134
+ assertNonEmptyString(options.clientId, "clientId");
5135
+ assertRedirectUri(options.redirectUri);
5136
+ const scope = assertScopes(options.scopes);
5137
+ let endpoint = options.authorizationEndpoint;
5138
+ let issuer = options.issuer;
5139
+ if (endpoint === void 0 || issuer === void 0) {
5140
+ const metadata = await this.getAuthorizationServerMetadata(options.issuer);
5141
+ endpoint ??= metadata.authorization_endpoint;
5142
+ issuer ??= metadata.issuer;
5143
+ }
5144
+ assertHttpsUrl(endpoint, "authorizationEndpoint");
5145
+ assertHttpsUrl(issuer, "issuer");
5146
+ const codeVerifier = options.codeVerifier ?? createCodeVerifier();
5147
+ assertCodeVerifier(codeVerifier);
5148
+ const state = options.state ?? createRandomValue(16);
5149
+ assertNonEmptyString(state, "state");
5150
+ const url = new URL(endpoint);
5151
+ url.searchParams.set("response_type", "code");
5152
+ url.searchParams.set("client_id", options.clientId);
5153
+ url.searchParams.set("redirect_uri", options.redirectUri);
5154
+ url.searchParams.set("scope", scope);
5155
+ url.searchParams.set("state", state);
5156
+ url.searchParams.set("code_challenge", codeChallengeFor(codeVerifier));
5157
+ url.searchParams.set("code_challenge_method", "S256");
5158
+ const { resource } = this.resourceParam(options.resource);
5159
+ if (resource !== void 0) url.searchParams.set("resource", resource);
5160
+ const wantsNonce = options.nonce === void 0 ? options.scopes.includes("openid") : options.nonce !== null;
5161
+ const nonce = wantsNonce ? options.nonce ?? createRandomValue(16) : void 0;
5162
+ if (nonce !== void 0) {
5163
+ assertNonEmptyString(nonce, "nonce");
5164
+ url.searchParams.set("nonce", nonce);
5165
+ }
5166
+ if (options.prompt !== void 0) {
5167
+ assertNonEmptyString(options.prompt, "prompt");
5168
+ url.searchParams.set("prompt", options.prompt);
5169
+ }
5170
+ this.logger.info("Built OAuth authorization URL");
5171
+ const request = {
5172
+ url: url.toString(),
5173
+ state,
5174
+ codeVerifier,
5175
+ issuer
5176
+ };
5177
+ if (nonce !== void 0) request.nonce = nonce;
5178
+ return request;
5179
+ }
5180
+ /**
5181
+ * Validate the authorization response that lands on your redirect URI and
5182
+ * return the code to exchange.
5183
+ *
5184
+ * Checks, in order and before anything else is trusted: `state` equals the
5185
+ * value from {@link OAuthResource.createAuthorizationUrl} (constant-time),
5186
+ * `iss` is present and equals the expected issuer, and only then whether
5187
+ * the server reported an error. A declined consent arrives as
5188
+ * `?error=access_denied`, not as a failed HTTP request.
5189
+ *
5190
+ * The `iss` check is strict because the authorization server advertises
5191
+ * RFC 9207 support and always sends the parameter: a missing `iss` is
5192
+ * treated exactly like a wrong one. Omit `expected.issuer` only if
5193
+ * something between the browser and your handler strips query parameters.
5194
+ *
5195
+ * This performs no network I/O.
5196
+ *
5197
+ * @param params - The callback's query parameters. Accepts an Express-style
5198
+ * `req.query` record, a `URLSearchParams`, a `URL`, a full callback URL
5199
+ * string, or a bare `a=b&c=d` query string.
5200
+ * @param expected - The stored {@link IOAuthAuthorizationRequest} (or any
5201
+ * object carrying its `state` and `issuer`).
5202
+ * @returns The validated response:
5203
+ * ```jsonc
5204
+ * {
5205
+ * "code": "def50200a1b2c3…",
5206
+ * "state": "8Xv2rQ7mJt0aLpKcWn4dZg",
5207
+ * "issuer": "https://auth.assinafy.com.br"
5208
+ * }
5209
+ * ```
5210
+ * @throws {ValidationError} If `state` is missing or does not match, `iss`
5211
+ * is absent or disagrees with the expected issuer, or a successful response
5212
+ * carries no `code`. In every case the response is not yours — stop, do not
5213
+ * exchange.
5214
+ * @throws {OAuthError} If the server returned `error` (e.g.
5215
+ * `access_denied`, `invalid_scope`, `invalid_request`,
5216
+ * `unsupported_response_type`, `invalid_target`).
5217
+ *
5218
+ * @example
5219
+ * ```ts
5220
+ * app.get('/oauth/callback', async (req, res) => {
5221
+ * const stored = req.session.oauth;
5222
+ * const { code } = client.oauth.readAuthorizationCallback(req.query, stored);
5223
+ * const tokens = await client.oauth.exchangeCode({
5224
+ * code,
5225
+ * codeVerifier: stored.codeVerifier,
5226
+ * redirectUri: 'https://myapp.com/oauth/callback',
5227
+ * clientId: process.env.ASSINAFY_CLIENT_ID!,
5228
+ * clientSecret: process.env.ASSINAFY_CLIENT_SECRET,
5229
+ * });
5230
+ * });
5231
+ * ```
5232
+ */
5233
+ readAuthorizationCallback(params, expected) {
5234
+ assertRecord(expected, "expected authorization request");
5235
+ assertNonEmptyString(expected.state, "expected.state");
5236
+ const query = toSearchParams(params);
5237
+ const state = query.get("state");
5238
+ if (state === null || !constantTimeEquals(state, expected.state)) {
5239
+ throw new ValidationError(
5240
+ "OAuth callback state does not match the stored authorization request"
5241
+ );
5242
+ }
5243
+ const issuer = query.get("iss") ?? void 0;
5244
+ if (expected.issuer !== void 0) {
5245
+ if (issuer === void 0 || normaliseIssuer(issuer) !== normaliseIssuer(expected.issuer)) {
5246
+ throw new ValidationError(
5247
+ "OAuth callback issuer is missing or does not match the expected issuer",
5248
+ { expected: expected.issuer, received: issuer ?? null }
5249
+ );
5250
+ }
5251
+ }
5252
+ const error = query.get("error");
5253
+ if (error !== null && error.length > 0) {
5254
+ throw new OAuthError(error, query.get("error_description"), 400, {
5255
+ error,
5256
+ error_description: query.get("error_description")
5257
+ });
5258
+ }
5259
+ const code = query.get("code");
5260
+ if (code === null || code.length === 0) {
5261
+ throw new ValidationError("OAuth callback carries neither a code nor an error");
5262
+ }
5263
+ const result = { code, state };
5264
+ if (issuer !== void 0) result.issuer = issuer;
5265
+ return result;
5266
+ }
5267
+ /**
5268
+ * Exchange an authorization code for tokens
5269
+ * (`POST /oauth/token`, `grant_type=authorization_code`).
5270
+ *
5271
+ * Run this on your server: the code is single-use and expires **60 seconds**
5272
+ * after approval, and a confidential application's secret must never reach
5273
+ * a browser. Every value must match the authorization request exactly, or
5274
+ * the API answers `invalid_grant`.
5275
+ *
5276
+ * @param options - Exchange options.
5277
+ * @param options.code - The code from
5278
+ * {@link OAuthResource.readAuthorizationCallback}.
5279
+ * @param options.codeVerifier - The verifier stored alongside the request.
5280
+ * @param options.redirectUri - The same redirect URI that was authorized.
5281
+ * @param options.clientId - The application's `client_id`.
5282
+ * @param options.clientSecret - The `client_secret`, for confidential
5283
+ * applications only. Public applications omit it and rely on PKCE.
5284
+ * @param options.resource - The same RFC 8707 resource indicator sent to
5285
+ * the authorization endpoint. Defaults to this API's origin; pass `null` to
5286
+ * omit it. A value disagreeing with the authorized one fails with
5287
+ * `invalid_target`.
5288
+ * @returns The token set — a flat object, **not** the API's usual envelope:
5289
+ * ```jsonc
5290
+ * {
5291
+ * "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…",
5292
+ * "token_type": "Bearer",
5293
+ * "expires_in": 3600,
5294
+ * "scope": "documents:read documents:write",
5295
+ * "refresh_token": "def5020088c2…", // only with offline_access
5296
+ * "id_token": "eyJraWQiOiJEQlR0S0…" // only with openid
5297
+ * }
5298
+ * ```
5299
+ * Read `scope` rather than assuming every requested permission was granted.
5300
+ * @throws {ValidationError} If an argument is missing or malformed, or a
5301
+ * `2xx` response carries no `access_token`.
5302
+ * @throws {OAuthError} `invalid_grant` for a spent, expired, replayed or
5303
+ * mismatched code; `invalid_client` for a bad `client_id`/`client_secret`;
5304
+ * `invalid_target` for a `resource` mismatch.
5305
+ *
5306
+ * @example
5307
+ * ```ts
5308
+ * const tokens = await client.oauth.exchangeCode({
5309
+ * code,
5310
+ * codeVerifier: session.oauth.codeVerifier,
5311
+ * redirectUri: 'https://myapp.com/oauth/callback',
5312
+ * clientId: process.env.ASSINAFY_CLIENT_ID!,
5313
+ * clientSecret: process.env.ASSINAFY_CLIENT_SECRET,
5314
+ * });
5315
+ * ```
5316
+ */
5317
+ async exchangeCode(options) {
5318
+ assertRecord(options, "code exchange options");
5319
+ assertNonEmptyString(options.code, "code");
5320
+ assertCodeVerifier(options.codeVerifier);
5321
+ assertRedirectUri(options.redirectUri);
5322
+ return this.requestToken("Failed to exchange the OAuth authorization code", {
5323
+ grant_type: "authorization_code",
5324
+ code: options.code,
5325
+ redirect_uri: options.redirectUri,
5326
+ code_verifier: options.codeVerifier,
5327
+ ...this.clientAuth(options),
5328
+ ...this.resourceParam(options.resource)
5329
+ });
5330
+ }
5331
+ /**
5332
+ * Renew an access token (`POST /oauth/token`, `grant_type=refresh_token`).
5333
+ *
5334
+ * Access tokens last one hour; refresh tokens are available only when
5335
+ * `offline_access` was requested and granted.
5336
+ *
5337
+ * **Refresh tokens rotate.** Every call returns a new one and retires the
5338
+ * one you sent, and a replayed refresh token cannot be told apart from a
5339
+ * stolen one — so the server ends the entire connection and the user must
5340
+ * reconnect. Therefore: persist `refresh_token` from the response before
5341
+ * doing anything else with it, treat a timeout as "it may have succeeded"
5342
+ * and re-read your stored token instead of retrying blindly, and never run
5343
+ * two refreshes concurrently for one connection.
5344
+ *
5345
+ * Refreshing does not extend the connection's 30-day life.
5346
+ *
5347
+ * @param options - Refresh options.
5348
+ * @param options.refreshToken - The current refresh token.
5349
+ * @param options.clientId - The application's `client_id`.
5350
+ * @param options.clientSecret - The `client_secret`, for confidential
5351
+ * applications only.
5352
+ * @param options.resource - RFC 8707 resource indicator. Defaults to this
5353
+ * API's origin; pass `null` to omit it.
5354
+ * @returns A fresh token set, identical in shape to
5355
+ * {@link OAuthResource.exchangeCode}:
5356
+ * ```jsonc
5357
+ * {
5358
+ * "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…",
5359
+ * "token_type": "Bearer",
5360
+ * "expires_in": 3600,
5361
+ * "scope": "documents:read documents:write",
5362
+ * "refresh_token": "def50200f1e2…" // NEW — persist it immediately
5363
+ * }
5364
+ * ```
5365
+ * @throws {ValidationError} If an argument is missing, or a `2xx` response
5366
+ * carries no `access_token`.
5367
+ * @throws {OAuthError} `invalid_grant` when the refresh token was already
5368
+ * used, expired, or the user reconnected with different permissions — ask
5369
+ * the user to reconnect. `invalid_client` for bad client credentials.
5370
+ *
5371
+ * @example
5372
+ * ```ts
5373
+ * const tokens = await client.oauth.refreshToken({
5374
+ * refreshToken: connection.refreshToken,
5375
+ * clientId: process.env.ASSINAFY_CLIENT_ID!,
5376
+ * clientSecret: process.env.ASSINAFY_CLIENT_SECRET,
5377
+ * });
5378
+ * await connection.save({ refreshToken: tokens.refresh_token });
5379
+ * ```
5380
+ */
5381
+ async refreshToken(options) {
5382
+ assertRecord(options, "refresh options");
5383
+ assertNonEmptyString(options.refreshToken, "refreshToken");
5384
+ return this.requestToken("Failed to refresh the OAuth access token", {
5385
+ grant_type: "refresh_token",
5386
+ refresh_token: options.refreshToken,
5387
+ ...this.clientAuth(options),
5388
+ ...this.resourceParam(options.resource)
5389
+ });
5390
+ }
5391
+ /**
5392
+ * Revoke an access or refresh token (`POST /oauth/revoke`, RFC 7009).
5393
+ *
5394
+ * Call this when a user disconnects your app, instead of only deleting your
5395
+ * copy of the token. Revoking a refresh token ends the whole connection.
5396
+ *
5397
+ * Every token outcome answers `200` — unknown, malformed and
5398
+ * already-revoked included — so the endpoint cannot be used to probe
5399
+ * whether a token exists. Only failed client authentication returns `401`.
5400
+ *
5401
+ * @param options - Revocation options.
5402
+ * @param options.token - The access or refresh token to revoke.
5403
+ * @param options.clientId - The application's `client_id`.
5404
+ * @param options.clientSecret - The `client_secret`, for confidential
5405
+ * applications only.
5406
+ * @param options.tokenTypeHint - Optional `access_token` or
5407
+ * `refresh_token` hint that lets the server skip a lookup.
5408
+ * @returns Nothing; resolves once the API acknowledges the request.
5409
+ * Request body:
5410
+ * ```jsonc
5411
+ * {
5412
+ * "token": "def50200f1e2…",
5413
+ * "token_type_hint": "refresh_token",
5414
+ * "client_id": "cli_1a2b3c",
5415
+ * "client_secret": "…"
5416
+ * }
5417
+ * ```
5418
+ * @throws {ValidationError} If `token` or `clientId` is missing, or
5419
+ * `tokenTypeHint` is not one of the two documented values.
5420
+ * @throws {OAuthError} `invalid_client` when client authentication fails.
5421
+ *
5422
+ * @example
5423
+ * ```ts
5424
+ * await client.oauth.revokeToken({
5425
+ * token: connection.refreshToken,
5426
+ * tokenTypeHint: 'refresh_token',
5427
+ * clientId: process.env.ASSINAFY_CLIENT_ID!,
5428
+ * clientSecret: process.env.ASSINAFY_CLIENT_SECRET,
5429
+ * });
5430
+ * ```
5431
+ */
5432
+ async revokeToken(options) {
5433
+ assertRecord(options, "revocation options");
5434
+ assertNonEmptyString(options.token, "token");
5435
+ if (options.tokenTypeHint !== void 0 && options.tokenTypeHint !== "access_token" && options.tokenTypeHint !== "refresh_token") {
5436
+ throw new ValidationError("tokenTypeHint must be access_token or refresh_token");
5437
+ }
5438
+ const body = cleanParams({
5439
+ token: options.token,
5440
+ token_type_hint: options.tokenTypeHint,
5441
+ ...this.clientAuth(options)
5442
+ });
5443
+ try {
5444
+ await this.callVoid(
5445
+ "Failed to revoke the OAuth token",
5446
+ () => this.publicHttp.post("/oauth/revoke", body)
5447
+ );
5448
+ } catch (error) {
5449
+ throw OAuthError.upgrade(error);
5450
+ }
5451
+ }
5452
+ /**
5453
+ * Read the OpenID Connect claims of the user who authorized a token
5454
+ * (`GET /oauth/userinfo`).
5455
+ *
5456
+ * Requires the `openid` scope; `name` additionally requires `profile` and
5457
+ * `email`/`email_verified` require `email`. Per OIDC Core §5.3.2 the
5458
+ * response is a flat claims object, not this API's usual envelope.
5459
+ *
5460
+ * @param accessToken - Token to introspect. Omit to use the credential the
5461
+ * client was constructed with (`token` or `apiKey`).
5462
+ * @returns The claims the granted scopes allow:
5463
+ * ```jsonc
5464
+ * {
5465
+ * "sub": "d6zqpbyog2v3xvxerwn8la94",
5466
+ * "name": "Maria Silva",
5467
+ * "email": "maria@example.com",
5468
+ * "email_verified": true
5469
+ * }
5470
+ * ```
5471
+ * `sub` is the stable user identifier; the rest are `null` when their scope
5472
+ * was not granted.
5473
+ * @throws {ValidationError} If `accessToken` is supplied but empty.
5474
+ * @throws {ApiError} `401` when the token is missing, expired or revoked;
5475
+ * `403` when the `openid` scope was not granted — its `WWW-Authenticate`
5476
+ * header names the scope to reconnect with.
5477
+ *
5478
+ * @example
5479
+ * ```ts
5480
+ * const who = await client.oauth.getUserInfo(tokens.access_token);
5481
+ * console.log(who.sub, who.email);
5482
+ * ```
5483
+ */
5484
+ async getUserInfo(accessToken) {
5485
+ if (accessToken === void 0) {
5486
+ return this.call(
5487
+ "Failed to fetch OAuth userinfo",
5488
+ () => this.http.get("/oauth/userinfo")
5489
+ );
5490
+ }
5491
+ assertNonEmptyString(accessToken, "accessToken");
5492
+ return this.call(
5493
+ "Failed to fetch OAuth userinfo",
5494
+ () => this.publicHttp.get("/oauth/userinfo", {
5495
+ headers: { Authorization: `Bearer ${accessToken}` }
5496
+ })
5497
+ );
5498
+ }
5499
+ /** POST the token endpoint and assert the response actually carries a token. */
5500
+ async requestToken(label, body) {
5501
+ let tokens;
5502
+ try {
5503
+ tokens = await this.call(
5504
+ label,
5505
+ () => this.publicHttp.post("/oauth/token", cleanParams(body))
5506
+ );
5507
+ } catch (error) {
5508
+ throw OAuthError.upgrade(error);
5509
+ }
5510
+ if (typeof tokens?.access_token !== "string" || tokens.access_token.length === 0) {
5511
+ throw new ValidationError(`${label}: the token endpoint returned no access_token`, {
5512
+ response: tokens
5513
+ });
5514
+ }
5515
+ return tokens;
5516
+ }
5517
+ /** `client_secret_post` credentials, omitting the secret for public clients. */
5518
+ clientAuth(options) {
5519
+ assertNonEmptyString(options.clientId, "clientId");
5520
+ if (options.clientSecret !== void 0) {
5521
+ assertNonEmptyString(options.clientSecret, "clientSecret");
5522
+ }
5523
+ return { client_id: options.clientId, client_secret: options.clientSecret };
5524
+ }
5525
+ /**
5526
+ * Resolve the optional RFC 8707 `resource` indicator.
5527
+ *
5528
+ * Defaults to the configured API origin, which is what this API publishes
5529
+ * as its `resource`. A loopback `http://` base URL — the shape used by mock
5530
+ * servers and the packed-consumer smoke test — has no valid resource
5531
+ * identifier, so the parameter is simply omitted rather than rejected; an
5532
+ * explicitly supplied value is still required to be `https`.
5533
+ */
5534
+ resourceParam(resource) {
5535
+ if (resource === void 0) {
5536
+ const origin = this.apiOrigin();
5537
+ return origin.startsWith("https:") ? { resource: origin } : {};
5538
+ }
5539
+ if (resource === null) return {};
5540
+ assertHttpsUrl(resource, "resource");
5541
+ return { resource };
5542
+ }
5543
+ /** Discover which authorization server may issue tokens for this API. */
5544
+ async defaultIssuer() {
5545
+ const metadata = await this.getProtectedResourceMetadata();
5546
+ const issuer = metadata?.authorization_servers?.[0];
5547
+ if (typeof issuer !== "string" || issuer.length === 0) {
5548
+ throw new ValidationError(
5549
+ "Protected-resource metadata lists no authorization server",
5550
+ { metadata }
5551
+ );
5552
+ }
5553
+ return issuer;
5554
+ }
5555
+ /**
5556
+ * Origin of the configured API host.
5557
+ *
5558
+ * The `.well-known` document and the RFC 8707 resource indicator both sit
5559
+ * at the host root, while `baseUrl` points at `/v1`.
5560
+ */
5561
+ apiOrigin() {
5562
+ const baseUrl = this.publicHttp.defaults.baseURL;
5563
+ if (typeof baseUrl !== "string" || baseUrl.length === 0) {
5564
+ throw new ValidationError("The client has no base URL to derive the API origin from");
5565
+ }
5566
+ return new URL(baseUrl).origin;
5567
+ }
5568
+ };
5569
+ function createCodeVerifier() {
5570
+ return (0, import_node_crypto.randomBytes)(32).toString("base64url");
5571
+ }
5572
+ function createRandomValue(bytes) {
5573
+ return (0, import_node_crypto.randomBytes)(bytes).toString("base64url");
5574
+ }
5575
+ function codeChallengeFor(codeVerifier) {
5576
+ return (0, import_node_crypto.createHash)("sha256").update(codeVerifier).digest("base64url");
5577
+ }
5578
+ function assertCodeVerifier(value) {
5579
+ if (typeof value !== "string" || !CODE_VERIFIER_PATTERN.test(value)) {
5580
+ throw new ValidationError(
5581
+ "codeVerifier must be 43-128 characters from A-Z a-z 0-9 - . _ ~"
5582
+ );
5583
+ }
5584
+ }
5585
+ function assertRedirectUri(value) {
5586
+ const uri = assertHttpsUrl(value, "redirectUri");
5587
+ if (uri.includes("#")) {
5588
+ throw new ValidationError("redirectUri must not contain a fragment");
5589
+ }
5590
+ }
5591
+ function assertHttpsUrl(value, label) {
5592
+ if (typeof value !== "string" || value.trim().length === 0) {
5593
+ throw new ValidationError(`${label} must be an absolute https URL`);
5594
+ }
5595
+ let url;
5596
+ try {
5597
+ url = new URL(value);
5598
+ } catch {
5599
+ throw new ValidationError(`${label} must be an absolute https URL`);
5600
+ }
5601
+ if (url.protocol !== "https:") {
5602
+ throw new ValidationError(`${label} must be an absolute https URL`);
5603
+ }
5604
+ return value;
5605
+ }
5606
+ function assertScopes(scopes) {
5607
+ if (!Array.isArray(scopes) || scopes.length === 0) {
5608
+ throw new ValidationError("scopes must be a non-empty array of scope strings");
5609
+ }
5610
+ for (const scope of scopes) {
5611
+ if (typeof scope !== "string" || scope.trim().length === 0 || /\s/u.test(scope)) {
5612
+ throw new ValidationError("each scope must be a non-empty string without whitespace");
5613
+ }
5614
+ }
5615
+ return [...new Set(scopes)].join(" ");
5616
+ }
5617
+ function normaliseIssuer(value) {
5618
+ return typeof value === "string" ? value.replace(/\/+$/u, "") : "";
5619
+ }
5620
+ function constantTimeEquals(left, right) {
5621
+ const a = Buffer.from(left, "utf8");
5622
+ const b = Buffer.from(right, "utf8");
5623
+ return a.length === b.length && (0, import_node_crypto.timingSafeEqual)(a, b);
5624
+ }
5625
+ function toSearchParams(params) {
5626
+ if (params instanceof URLSearchParams) return params;
5627
+ if (params instanceof URL) return params.searchParams;
5628
+ if (typeof params === "string") {
5629
+ return params.includes("://") ? new URL(params).searchParams : new URLSearchParams(params.replace(/^\?/u, ""));
5630
+ }
5631
+ assertRecord(params, "callback parameters");
5632
+ const search = new URLSearchParams();
5633
+ for (const [key, value] of Object.entries(params)) {
5634
+ const first = Array.isArray(value) ? value[0] : value;
5635
+ if (typeof first === "string") search.set(key, first);
5636
+ }
5637
+ return search;
5638
+ }
5639
+
4840
5640
  // src/resources/fields.ts
4841
5641
  var FieldsResource = class extends BaseResource {
4842
5642
  /**
@@ -6060,7 +6860,7 @@ function validateNotificationPreferences(preferences) {
6060
6860
  }
6061
6861
 
6062
6862
  // src/support/webhook-verifier.ts
6063
- var import_node_crypto = require("crypto");
6863
+ var import_node_crypto2 = require("crypto");
6064
6864
  var WebhookVerifier = class {
6065
6865
  webhookSecret;
6066
6866
  /**
@@ -6099,10 +6899,10 @@ var WebhookVerifier = class {
6099
6899
  const buf = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
6100
6900
  const provided = signature.trim();
6101
6901
  if (!/^[\da-f]{64}$/i.test(provided)) return false;
6102
- const expected = (0, import_node_crypto.createHmac)("sha256", this.webhookSecret).update(buf).digest();
6902
+ const expected = (0, import_node_crypto2.createHmac)("sha256", this.webhookSecret).update(buf).digest();
6103
6903
  const actual = Buffer.from(provided, "hex");
6104
6904
  try {
6105
- return (0, import_node_crypto.timingSafeEqual)(expected, actual);
6905
+ return (0, import_node_crypto2.timingSafeEqual)(expected, actual);
6106
6906
  } catch {
6107
6907
  return false;
6108
6908
  }
@@ -6187,6 +6987,7 @@ var AssinafyClient = class _AssinafyClient {
6187
6987
  templates;
6188
6988
  tags;
6189
6989
  auth;
6990
+ oauth;
6190
6991
  fields;
6191
6992
  signerDocuments;
6192
6993
  users;
@@ -6293,6 +7094,12 @@ var AssinafyClient = class _AssinafyClient {
6293
7094
  this.logger,
6294
7095
  this.publicAxiosInstance
6295
7096
  );
7097
+ this.oauth = new OAuthResource(
7098
+ this.axiosInstance,
7099
+ void 0,
7100
+ this.logger,
7101
+ this.publicAxiosInstance
7102
+ );
6296
7103
  this.fields = new FieldsResource(this.axiosInstance, this.defaultAccountId, this.logger);
6297
7104
  this.signerDocuments = new SignerDocumentsResource(
6298
7105
  this.publicAxiosInstance,
@@ -6730,6 +7537,8 @@ function hasIdempotencyKey(headers) {
6730
7537
  MAX_LIST_PAGE_SIZE,
6731
7538
  MAX_UPLOAD_BYTES,
6732
7539
  NetworkError,
7540
+ OAuthError,
7541
+ OAuthResource,
6733
7542
  SDK_USER_AGENT,
6734
7543
  SignerDocumentsResource,
6735
7544
  SignerResource,
@@ -6740,5 +7549,6 @@ function hasIdempotencyKey(headers) {
6740
7549
  WebhookResource,
6741
7550
  WebhookVerifier,
6742
7551
  WorkspaceResource,
6743
- buildAssignmentPayload
7552
+ buildAssignmentPayload,
7553
+ parseWwwAuthenticate
6744
7554
  });