@scalekit-sdk/node 2.1.6 → 2.1.8

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.
Files changed (92) hide show
  1. package/buf.gen.yaml +1 -0
  2. package/lib/auth.d.ts +41 -9
  3. package/lib/auth.js +44 -12
  4. package/lib/auth.js.map +1 -1
  5. package/lib/connection.d.ts +195 -21
  6. package/lib/connection.js +197 -23
  7. package/lib/connection.js.map +1 -1
  8. package/lib/core.d.ts +2 -2
  9. package/lib/core.js +13 -12
  10. package/lib/core.js.map +1 -1
  11. package/lib/directory.d.ts +293 -40
  12. package/lib/directory.js +308 -44
  13. package/lib/directory.js.map +1 -1
  14. package/lib/domain.d.ts +166 -18
  15. package/lib/domain.js +178 -29
  16. package/lib/domain.js.map +1 -1
  17. package/lib/organization.d.ts +407 -40
  18. package/lib/organization.js +433 -49
  19. package/lib/organization.js.map +1 -1
  20. package/lib/permission.d.ts +179 -35
  21. package/lib/permission.js +190 -38
  22. package/lib/permission.js.map +1 -1
  23. package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.d.ts +3 -3
  24. package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.js +2 -2
  25. package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.js.map +1 -1
  26. package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.d.ts +16 -16
  27. package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.js +21 -21
  28. package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.js.map +1 -1
  29. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.d.ts +82 -0
  30. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.js +90 -0
  31. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.js.map +1 -0
  32. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.d.ts +647 -0
  33. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.js +993 -0
  34. package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.js.map +1 -0
  35. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.d.ts +142 -0
  36. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js +165 -1
  37. package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js.map +1 -1
  38. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.d.ts +1 -10
  39. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js +0 -9
  40. package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js.map +1 -1
  41. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.d.ts +28 -63
  42. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js +9 -90
  43. package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js.map +1 -1
  44. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.d.ts +6 -6
  45. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js +5 -5
  46. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.d.ts +19 -30
  47. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js +22 -31
  48. package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js.map +1 -1
  49. package/lib/pkg/grpc/scalekit/v1/users/users_pb.d.ts +2 -2
  50. package/lib/pkg/grpc/scalekit/v1/users/users_pb.js +1 -1
  51. package/lib/pkg/grpc/scalekit/v1/users/users_pb.js.map +1 -1
  52. package/lib/role.d.ts +252 -56
  53. package/lib/role.js +262 -62
  54. package/lib/role.js.map +1 -1
  55. package/lib/scalekit.d.ts +323 -54
  56. package/lib/scalekit.js +354 -76
  57. package/lib/scalekit.js.map +1 -1
  58. package/lib/session.d.ts +235 -22
  59. package/lib/session.js +237 -24
  60. package/lib/session.js.map +1 -1
  61. package/lib/types/organization.d.ts +3 -0
  62. package/lib/user.d.ts +571 -53
  63. package/lib/user.js +598 -89
  64. package/lib/user.js.map +1 -1
  65. package/lib/webauthn.d.ts +33 -0
  66. package/lib/webauthn.js +80 -0
  67. package/lib/webauthn.js.map +1 -0
  68. package/package.json +2 -2
  69. package/src/auth.ts +53 -19
  70. package/src/connection.ts +237 -62
  71. package/src/core.ts +39 -33
  72. package/src/directory.ts +356 -98
  73. package/src/domain.ts +215 -68
  74. package/src/organization.ts +506 -105
  75. package/src/permission.ts +234 -88
  76. package/src/pkg/grpc/scalekit/v1/auth/auth_connect.ts +3 -3
  77. package/src/pkg/grpc/scalekit/v1/auth/auth_pb.ts +24 -24
  78. package/src/pkg/grpc/scalekit/v1/auth/webauthn_connect.ts +89 -0
  79. package/src/pkg/grpc/scalekit/v1/auth/webauthn_pb.ts +1263 -0
  80. package/src/pkg/grpc/scalekit/v1/commons/commons_pb.ts +217 -0
  81. package/src/pkg/grpc/scalekit/v1/connections/connections_connect.ts +1 -10
  82. package/src/pkg/grpc/scalekit/v1/connections/connections_pb.ts +42 -129
  83. package/src/pkg/grpc/scalekit/v1/organizations/organizations_connect.ts +6 -6
  84. package/src/pkg/grpc/scalekit/v1/organizations/organizations_pb.ts +28 -43
  85. package/src/pkg/grpc/scalekit/v1/users/users_pb.ts +3 -3
  86. package/src/role.ts +336 -136
  87. package/src/scalekit.ts +478 -149
  88. package/src/session.ts +266 -63
  89. package/src/types/organization.ts +4 -0
  90. package/src/user.ts +675 -168
  91. package/src/webauthn.ts +98 -0
  92. package/tests/organization.test.ts +16 -0
package/src/scalekit.ts CHANGED
@@ -14,6 +14,7 @@ import UserClient from './user';
14
14
  import SessionClient from './session';
15
15
  import RoleClient from './role';
16
16
  import PermissionClient from './permission';
17
+ import WebAuthnClient from './webauthn';
17
18
  import { IdpInitiatedLoginClaims, IdTokenClaim, User } from './types/auth';
18
19
  import { AuthenticationOptions, AuthenticationResponse, AuthorizationUrlOptions, GrantType, LogoutUrlOptions, RefreshTokenResponse ,TokenValidationOptions } from './types/scalekit';
19
20
  import { WebhookVerificationError, ScalekitValidateTokenFailureException } from './errors/base-exception';
@@ -24,14 +25,35 @@ const WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; // 5 minutes
24
25
  const WEBHOOK_SIGNATURE_VERSION = "v1";
25
26
 
26
27
  /**
27
- * To initiate scalekit
28
- * @param {string} envUrl The environment url
29
- * @param {string} clientId The client id
30
- * @param {string} clientSecret The client secret
31
- * @returns {ScalekitClient} Returns the scalekit instance
28
+ * Main Scalekit SDK client for interacting with all Scalekit API endpoints.
29
+ *
30
+ * TIP: You can use it as a singleton object - that is you can initialize it just once and use the same client variable wherever required.
31
+ *
32
+ * This is the primary entry point for interacting with Scalekit's authentication services,
33
+ * including SSO, SCIM, user management, roles, permissions, and passwordless authentication.
34
+ *
35
+ * You can find the Environment URL, Client ID and Client Secret in Scalekit Dashboard -> Developers (Settings) -> API Credentials
36
+ *
37
+ * @param {string} envUrl - The Scalekit environment URL (e.g., "https://yourorg.scalekit.com" or your configured custom domain like "https://auth.yourapp.ai")
38
+ * @param {string} clientId - Your Scalekit client ID from the Scalekit Dashboard
39
+ * @param {string} clientSecret - Your Scalekit client secret from the Scalekit Dashboard
40
+ *
32
41
  * @example
33
- * const scalekit = new Scalekit(envUrl, clientId, clientSecret);
34
- */
42
+ * // Initialize the Scalekit client
43
+ * import { ScalekitClient } from '@scalekit-sdk/node';
44
+ *
45
+ * const scalekitClient = new ScalekitClient(
46
+ * process.env.SCALEKIT_ENV_URL,
47
+ * process.env.SCALEKIT_CLIENT_ID,
48
+ * process.env.SCALEKIT_CLIENT_SECRET
49
+ * );
50
+ *
51
+ * // Access various client modules
52
+ * const organizations = await scalekitClient.organization.listOrganization();
53
+ * const users = await scalekitClient.user.listUsers();
54
+ *
55
+ * @see {@link https://docs.scalekit.com/apis/ | Scalekit API Documentation}
56
+ */
35
57
  export default class ScalekitClient {
36
58
  private readonly coreClient: CoreClient;
37
59
  private readonly grpcConnect: GrpcConnect;
@@ -45,6 +67,7 @@ export default class ScalekitClient {
45
67
  readonly role: RoleClient;
46
68
  readonly permission: PermissionClient;
47
69
  readonly auth: AuthClient;
70
+ readonly webauthn: WebAuthnClient;
48
71
  constructor(
49
72
  envUrl: string,
50
73
  clientId: string,
@@ -63,18 +86,9 @@ export default class ScalekitClient {
63
86
  this.grpcConnect,
64
87
  this.coreClient
65
88
  );
66
- this.connection = new ConnectionClient(
67
- this.grpcConnect,
68
- this.coreClient
69
- );
70
- this.domain = new DomainClient(
71
- this.grpcConnect,
72
- this.coreClient
73
- );
74
- this.directory = new DirectoryClient(
75
- this.grpcConnect,
76
- this.coreClient
77
- );
89
+ this.connection = new ConnectionClient(this.grpcConnect, this.coreClient);
90
+ this.domain = new DomainClient(this.grpcConnect, this.coreClient);
91
+ this.directory = new DirectoryClient(this.grpcConnect, this.coreClient);
78
92
  this.passwordless = new PasswordlessClient(
79
93
  this.grpcConnect,
80
94
  this.coreClient
@@ -99,45 +113,94 @@ export default class ScalekitClient {
99
113
  this.grpcConnect,
100
114
  this.coreClient
101
115
  );
116
+ this.webauthn = new WebAuthnClient(
117
+ this.grpcConnect,
118
+ this.coreClient
119
+ );
102
120
  }
103
121
 
104
122
  /**
105
- * Returns the authorization url to initiate the authentication request.
106
- * @param {string} redirectUri Redirect uri
107
- * @param {AuthorizationUrlOptions} options Authorization url options
108
- * @param {string[]} options.scopes Scopes to request from the user
109
- * @param {string} options.state State parameter
110
- * @param {string} options.nonce Nonce parameter
111
- * @param {string} options.loginHint Login hint parameter
112
- * @param {string} options.domainHint Domain hint parameter
113
- * @param {string} options.connectionId Connection id parameter
114
- * @param {string} options.organizationId Organization id parameter
115
- * @param {string} options.provider Provider i.e. google, github, etc.
116
- * @param {string} options.codeChallenge Code challenge parameter in case of PKCE
117
- * @param {string} options.codeChallengeMethod Code challenge method parameter in case of PKCE
118
- * @param {string} options.prompt Prompt parameter to control the authorization server's authentication behavior
119
- *
123
+ * Utility method to generate the OAuth 2.0 authorization URL to initiate the SSO authentication flow.
124
+ *
125
+ * This method doesn't make any network calls but instead generates a fully formed Authorization URL
126
+ * as a string that you can redirect your users to initiate authentication.
127
+ *
128
+ * @param {string} redirectUri - The URL where users will be redirected after authentication.
129
+ * Must match one of the redirect URIs configured in your Scalekit dashboard.
130
+ * @param {AuthorizationUrlOptions} [options] - Optional configuration for the authorization request
131
+ * @param {string[]} [options.scopes=['openid', 'profile', 'email']] - OAuth scopes to request. Default includes openid, profile, and email.
132
+ * @param {string} [options.state] - Opaque value to maintain state between request and callback. Used to prevent CSRF attacks.
133
+ * @param {string} [options.nonce] - String value used to associate a client session with an ID Token.
134
+ * @param {string} [options.loginHint] - Hint to the authorization server about the login identifier the user might use (e.g., email address).
135
+ * @param {string} [options.domainHint] - Domain hint to identify which organization's IdP to use for authentication.
136
+ * @param {string} [options.connectionId] - Specific SSO connection ID to use for authentication.
137
+ * @param {string} [options.organizationId] - Organization ID to authenticate against.
138
+ * @param {string} [options.provider] - Social login provider (e.g., 'google', 'github', 'microsoft').
139
+ * @param {string} [options.codeChallenge] - PKCE code challenge for enhanced security in public clients.
140
+ * @param {string} [options.codeChallengeMethod] - Method used to generate the code challenge (we support only 'S256').
141
+ * @param {string} [options.prompt] - Controls the authorization server's authentication behavior (e.g., 'login', 'consent', 'create').
142
+ *
143
+ * @returns {string} The complete authorization URL to redirect the user to
144
+ *
120
145
  * @example
121
- * const scalekit = new Scalekit(envUrl, clientId, clientSecret);
122
- * const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, {
123
- * scopes: ['openid', 'profile'],
124
- * prompt: 'create'
125
- * });
126
- * @returns {string} authorization url
146
+ * // Initiate Enterprise SSO authentication for a given org_id
147
+ * const authUrl = scalekitClient.getAuthorizationUrl(
148
+ * 'https://yourapp.com/auth/callback',
149
+ * {
150
+ * state: 'random-state-value',
151
+ * organizationId: 'org_123456'
152
+ * }
153
+ * );
154
+ * // Redirect user to authUrl
155
+ *
156
+ * @example
157
+ * // Initiate Enterprise SSO authentication for a specific connection id
158
+ * // optionally, pass the loginhint to the 3rd party identity provider.
159
+ * const authUrl = scalekitClient.getAuthorizationUrl(
160
+ * 'https://yourapp.com/auth/callback',
161
+ * {
162
+ * connectionId: 'conn_abc123',
163
+ * loginHint: 'user@company.com'
164
+ * }
165
+ * );
166
+ *
167
+ * @example
168
+ * // Social login
169
+ * const authUrl = scalekitClient.getAuthorizationUrl(
170
+ * 'https://yourapp.com/auth/callback',
171
+ * {
172
+ * provider: 'google',
173
+ * state: 'random-state'
174
+ * }
175
+ * );
176
+ *
177
+ * @example
178
+ * // PKCE flow for public clients
179
+ * const authUrl = scalekitClient.getAuthorizationUrl(
180
+ * 'https://yourapp.com/auth/callback',
181
+ * {
182
+ * codeChallenge: 'your-code-challenge',
183
+ * codeChallengeMethod: 'S256',
184
+ * organizationId: 'org_123456'
185
+ * }
186
+ * );
187
+ *
188
+ * @see {@link https://docs.scalekit.com/apis/#tag/api%20auth | Authentication API Documentation}
189
+ * @see {@link authenticateWithCode} - Use this method to exchange the authorization code for tokens
127
190
  */
128
191
  getAuthorizationUrl(
129
192
  redirectUri: string,
130
193
  options?: AuthorizationUrlOptions
131
194
  ): string {
132
195
  const defaultOptions: AuthorizationUrlOptions = {
133
- scopes: ['openid', 'profile', 'email']
134
- }
196
+ scopes: ["openid", "profile", "email"],
197
+ };
135
198
  options = {
136
199
  ...defaultOptions,
137
- ...options
138
- }
200
+ ...options,
201
+ };
139
202
  const qs = QueryString.stringify({
140
- response_type: 'code',
203
+ response_type: "code",
141
204
  client_id: this.coreClient.clientId,
142
205
  redirect_uri: redirectUri,
143
206
  scope: options.scopes?.join(" "),
@@ -147,38 +210,99 @@ export default class ScalekitClient {
147
210
  ...(options.domainHint && { domain_hint: options.domainHint }),
148
211
  ...(options.domainHint && { domain: options.domainHint }),
149
212
  ...(options.connectionId && { connection_id: options.connectionId }),
150
- ...(options.organizationId && { organization_id: options.organizationId }),
213
+ ...(options.organizationId && {
214
+ organization_id: options.organizationId,
215
+ }),
151
216
  ...(options.codeChallenge && { code_challenge: options.codeChallenge }),
152
- ...(options.codeChallengeMethod && { code_challenge_method: options.codeChallengeMethod }),
217
+ ...(options.codeChallengeMethod && {
218
+ code_challenge_method: options.codeChallengeMethod,
219
+ }),
153
220
  ...(options.provider && { provider: options.provider }),
154
- ...(options.prompt && { prompt: options.prompt })
155
- })
221
+ ...(options.prompt && { prompt: options.prompt }),
222
+ });
156
223
 
157
- return `${this.coreClient.envUrl}/${authorizeEndpoint}?${qs}`
224
+ return `${this.coreClient.envUrl}/${authorizeEndpoint}?${qs}`;
158
225
  }
159
226
 
160
227
  /**
161
- * Authenticate with the code
162
- * @param {string} code Code
163
- * @param {string} redirectUri Redirect uri
164
- * @param {AuthenticationOptions} options Code authentication options
165
- * @param {string} options.codeVerifier Code verifier in case of PKCE
166
- * @returns {Promise<AuthenticationResponse>} Returns user, id token and access token
228
+ * Exchanges an authorization code for access tokens and user information.
229
+ *
230
+ * This method completes the OAuth 2.0 authorization code flow by exchanging the code
231
+ * received in the callback for access tokens, ID tokens, and user profile information.
232
+ * Call this method in your redirect URI handler after receiving the authorization code.
233
+ *
234
+ * @param {string} code - The authorization code received in the callback URL after user authentication
235
+ * @param {string} redirectUri - The same redirect URI used in getAuthorizationUrl(). Must match exactly.
236
+ * @param {AuthenticationOptions} [options] - Optional authentication configuration
237
+ * @param {string} [options.codeVerifier] - PKCE code verifier to validate the code challenge (required if PKCE was used)
238
+ *
239
+ * @returns {Promise<AuthenticationResponse>} Authentication response containing:
240
+ * - user: User profile information (email, name, organization, etc.)
241
+ * - idToken: JWT ID token containing user claims
242
+ * - accessToken: Access token for API authorization
243
+ * - expiresIn: Token expiration time in seconds
244
+ * - refreshToken: Refresh token for obtaining new access tokens
245
+ *
246
+ * @throws {Error} When the authorization code is invalid, expired, or already used
247
+ * @throws {Error} When the redirect URI doesn't match the one used in authorization
248
+ * @throws {Error} When PKCE code verifier is invalid or missing
249
+ *
250
+ * @example
251
+ * // Basic code exchange (server-side flow)
252
+ * app.get('/auth/callback', async (req, res) => {
253
+ * const { code } = req.query;
254
+ *
255
+ * try {
256
+ * const result = await scalekitClient.authenticateWithCode(
257
+ * code,
258
+ * 'https://yourapp.com/auth/callback'
259
+ * );
260
+ *
261
+ * // Store tokens securely
262
+ * req.session.accessToken = result.accessToken;
263
+ * req.session.user = result.user;
264
+ *
265
+ * res.redirect('/dashboard');
266
+ * } catch (error) {
267
+ * console.error('Authentication failed:', error);
268
+ * res.redirect('/login?error=auth_failed');
269
+ * }
270
+ * });
271
+ *
272
+ * @example
273
+ * // PKCE flow (for public clients)
274
+ * app.get('/auth/callback', async (req, res) => {
275
+ * const { code } = req.query;
276
+ * const codeVerifier = req.session.codeVerifier; // Stored during authorization
277
+ *
278
+ * const result = await scalekitClient.authenticateWithCode(
279
+ * code,
280
+ * 'https://yourapp.com/auth/callback',
281
+ * { codeVerifier }
282
+ * );
283
+ *
284
+ * // Use result.user, result.accessToken, etc.
285
+ * });
286
+ *
287
+ * @see {@link getAuthorizationUrl} - Generate the authorization URL first
288
+ * @see {@link validateAccessToken} - Validate tokens in subsequent requests
167
289
  */
168
290
  async authenticateWithCode(
169
291
  code: string,
170
292
  redirectUri: string,
171
- options?: AuthenticationOptions,
293
+ options?: AuthenticationOptions
172
294
  ): Promise<AuthenticationResponse> {
173
- const res = await this.coreClient.authenticate(QueryString.stringify({
174
- code: code,
175
- redirect_uri: redirectUri,
176
- grant_type: GrantType.AuthorizationCode,
177
- client_id: this.coreClient.clientId,
178
- client_secret: this.coreClient.clientSecret,
179
- ...(options?.codeVerifier && { code_verifier: options.codeVerifier })
180
- }))
181
- const { id_token, access_token, expires_in , refresh_token } = res.data;
295
+ const res = await this.coreClient.authenticate(
296
+ QueryString.stringify({
297
+ code: code,
298
+ redirect_uri: redirectUri,
299
+ grant_type: GrantType.AuthorizationCode,
300
+ client_id: this.coreClient.clientId,
301
+ client_secret: this.coreClient.clientSecret,
302
+ ...(options?.codeVerifier && { code_verifier: options.codeVerifier }),
303
+ })
304
+ );
305
+ const { id_token, access_token, expires_in, refresh_token } = res.data;
182
306
  const claims = jose.decodeJwt<IdTokenClaim>(id_token);
183
307
  const user = <User>{};
184
308
  for (const [k, v] of Object.entries(claims)) {
@@ -192,29 +316,83 @@ export default class ScalekitClient {
192
316
  idToken: id_token,
193
317
  accessToken: access_token,
194
318
  expiresIn: expires_in,
195
- refreshToken: refresh_token
196
- }
319
+ refreshToken: refresh_token,
320
+ };
197
321
  }
198
322
 
199
323
  /**
200
- * Get the idp initiated login claims
201
- *
202
- * @param {string} idpInitiatedLoginToken The idp_initiated_login query param from the URL
203
- * @param {TokenValidationOptions} options Optional validation options for issuer and audience
204
- * @returns {object} Returns the idp initiated login claims
205
- */
206
- async getIdpInitiatedLoginClaims(idpInitiatedLoginToken: string, options?: TokenValidationOptions): Promise<IdpInitiatedLoginClaims> {
207
- return this.validateToken<IdpInitiatedLoginClaims>(idpInitiatedLoginToken, options);
324
+ * Extracts and validates claims from an IdP-initiated login token.
325
+ *
326
+ * Use this method when handling IdP-initiated SSO flows, where the authentication is
327
+ * initiated from the identity provider's portal rather than your application. This validates
328
+ * the token and returns the necessary information to initiate a new SP Initiated SSO workflow.
329
+ *
330
+ * @param {string} idpInitiatedLoginToken - The token received in the 'idp_initiated_login' query parameter
331
+ * @param {TokenValidationOptions} [options] - Optional token validation configuration
332
+ * @param {string} [options.issuer] - Expected token issuer for validation
333
+ * @param {string} [options.audience] - Expected token audience for validation
334
+ *
335
+ * @returns {Promise<IdpInitiatedLoginClaims>} Claims containing:
336
+ * - connection_id: The SSO connection identifier
337
+ * - organization_id: The organization identifier
338
+ * - login_hint: User's email or login identifier
339
+ * - relay_state: Optional state parameter from the IdP
340
+ *
341
+ * @throws {ScalekitValidateTokenFailureException} When token validation fails
342
+ *
343
+ * @example
344
+ * // Handle IdP-initiated login
345
+ * app.get('/auth/callback', async (req, res) => {
346
+ * const { idp_initiated_login } = req.query;
347
+ *
348
+ * if (idp_initiated_login) {
349
+ * try {
350
+ * const claims = await scalekitClient.getIdpInitiatedLoginClaims(idp_initiated_login);
351
+ *
352
+ * // Redirect to authorization URL with the claims
353
+ * const authUrl = scalekitClient.getAuthorizationUrl(
354
+ * 'https://yourapp.com/auth/callback',
355
+ * {
356
+ * connectionId: claims.connection_id,
357
+ * organizationId: claims.organization_id,
358
+ * loginHint: claims.login_hint,
359
+ * ...(claims.relay_state && { state: claims.relay_state })
360
+ * }
361
+ * );
362
+ *
363
+ * return res.redirect(authUrl);
364
+ * } catch (error) {
365
+ * console.error('IdP-initiated login failed:', error);
366
+ * return res.redirect('/login?error=idp_login_failed');
367
+ * }
368
+ * }
369
+ * // Handle normal callback flow...
370
+ * });
371
+ *
372
+ * @see {@link https://docs.scalekit.com/sso/guides/idp-init-sso/ | IdP-Initiated SSO Documentation}
373
+ * @see {@link getAuthorizationUrl} - Use the claims to construct the authorization URL
374
+ */
375
+ async getIdpInitiatedLoginClaims(
376
+ idpInitiatedLoginToken: string,
377
+ options?: TokenValidationOptions
378
+ ): Promise<IdpInitiatedLoginClaims> {
379
+ return this.validateToken<IdpInitiatedLoginClaims>(
380
+ idpInitiatedLoginToken,
381
+ options
382
+ );
208
383
  }
209
384
 
210
385
  /**
211
386
  * Validates the access token and returns a boolean result.
212
- *
387
+ *
213
388
  * @param {string} token The token to be validated.
214
389
  * @param {TokenValidationOptions} options Optional validation options for issuer, audience, and scopes
215
390
  * @return {Promise<boolean>} Returns true if the token is valid, false otherwise.
216
391
  */
217
- async validateAccessToken(token: string, options?: TokenValidationOptions): Promise<boolean> {
392
+ async validateAccessToken(
393
+ token: string,
394
+ options?: TokenValidationOptions
395
+ ): Promise<boolean> {
218
396
  try {
219
397
  await this.validateToken(token, options);
220
398
  return true;
@@ -223,8 +401,6 @@ export default class ScalekitClient {
223
401
  }
224
402
  }
225
403
 
226
-
227
-
228
404
  /**
229
405
  * Returns the logout URL that can be used to log out the user.
230
406
  * @param {LogoutUrlOptions} options Logout URL options
@@ -232,7 +408,7 @@ export default class ScalekitClient {
232
408
  * @param {string} options.postLogoutRedirectUri URL to redirect after logout
233
409
  * @param {string} options.state Opaque value to maintain state between request and callback
234
410
  * @returns {string} The logout URL
235
- *
411
+ *
236
412
  * @example
237
413
  * const scalekit = new Scalekit(envUrl, clientId, clientSecret);
238
414
  * const logoutUrl = scalekit.getLogoutUrl({
@@ -243,48 +419,118 @@ export default class ScalekitClient {
243
419
  getLogoutUrl(options?: LogoutUrlOptions): string {
244
420
  const qs = QueryString.stringify({
245
421
  ...(options?.idTokenHint && { id_token_hint: options.idTokenHint }),
246
- ...(options?.postLogoutRedirectUri && { post_logout_redirect_uri: options.postLogoutRedirectUri }),
247
- ...(options?.state && { state: options.state })
422
+ ...(options?.postLogoutRedirectUri && {
423
+ post_logout_redirect_uri: options.postLogoutRedirectUri,
424
+ }),
425
+ ...(options?.state && { state: options.state }),
248
426
  });
249
427
 
250
- return `${this.coreClient.envUrl}/${logoutEndpoint}${qs ? `?${qs}` : ''}`;
428
+ return `${this.coreClient.envUrl}/${logoutEndpoint}${qs ? `?${qs}` : ""}`;
251
429
  }
252
430
 
253
431
  /**
254
- * Verify webhook payload
255
- *
256
- * @param {string} secret The secret
257
- * @param {Record<string, string>} headers The headers
258
- * @param {string} payload The payload
259
- * @return {boolean} Returns true if the payload is valid.
432
+ * Verifies the authenticity and integrity of webhook payloads from Scalekit.
433
+ *
434
+ * Use this method to validate webhook requests from Scalekit by verifying the HMAC signature.
435
+ * This ensures the webhook was sent by Scalekit and hasn't been tampered with. The method
436
+ * checks the signature and timestamp to prevent replay attacks (5-minute tolerance window).
437
+ *
438
+ * @param {string} secret - Your webhook signing secret from the Scalekit dashboard (format: 'whsec_...')
439
+ * @param {Record<string, string>} headers - The HTTP headers from the webhook request
440
+ * @param {string} payload - The raw webhook request body as a string
441
+ *
442
+ * @returns {boolean} Returns true if the webhook signature is valid
443
+ *
444
+ * @throws {WebhookVerificationError} When required headers are missing
445
+ * @throws {WebhookVerificationError} When the secret format is invalid
446
+ * @throws {WebhookVerificationError} When the signature doesn't match
447
+ * @throws {WebhookVerificationError} When the timestamp is too old (>5 minutes) or in the future
448
+ *
449
+ * @example
450
+ * // Express.js webhook handler
451
+ * app.post('/webhooks/scalekit', express.raw({ type: 'application/json' }), (req, res) => {
452
+ * const secret = process.env.SCALEKIT_WEBHOOK_SECRET;
453
+ * const headers = req.headers;
454
+ * const payload = req.body.toString();
455
+ *
456
+ * try {
457
+ * const isValid = scalekitClient.verifyWebhookPayload(secret, headers, payload);
458
+ *
459
+ * if (isValid) {
460
+ * const event = JSON.parse(payload);
461
+ *
462
+ * // Process the webhook event
463
+ * switch (event.type) {
464
+ * case 'user.created':
465
+ * console.log('New user created:', event.data);
466
+ * break;
467
+ * case 'connection.enabled':
468
+ * console.log('Connection enabled:', event.data);
469
+ * break;
470
+ * }
471
+ *
472
+ * res.status(200).send('Webhook received');
473
+ * }
474
+ * } catch (error) {
475
+ * console.error('Webhook verification failed:', error);
476
+ * res.status(400).send('Invalid webhook signature');
477
+ * }
478
+ * });
479
+ *
480
+ * @see {@link https://docs.scalekit.com/reference/webhooks/overview/ | Webhook Documentation}
481
+ * @see {@link verifyInterceptorPayload} - Similar method for interceptor payloads
260
482
  */
261
- verifyWebhookPayload(secret: string, headers: Record<string, string>, payload: string): boolean {
262
- const webhookId = headers['webhook-id'];
263
- const webhookTimestamp = headers['webhook-timestamp'];
264
- const webhookSignature = headers['webhook-signature'];
265
-
266
- return this.verifyPayloadSignature(secret, webhookId, webhookTimestamp, webhookSignature, payload);
483
+ verifyWebhookPayload(
484
+ secret: string,
485
+ headers: Record<string, string>,
486
+ payload: string
487
+ ): boolean {
488
+ const webhookId = headers["webhook-id"];
489
+ const webhookTimestamp = headers["webhook-timestamp"];
490
+ const webhookSignature = headers["webhook-signature"];
491
+
492
+ return this.verifyPayloadSignature(
493
+ secret,
494
+ webhookId,
495
+ webhookTimestamp,
496
+ webhookSignature,
497
+ payload
498
+ );
267
499
  }
268
500
 
269
501
  /**
270
- * Verify interceptor payload
271
- *
272
- * @param {string} secret The secret
273
- * @param {Record<string, string>} headers The headers
274
- * @param {string} payload The payload
275
- * @return {boolean} Returns true if the payload is valid.
502
+ * Verifies the authenticity and integrity of interceptor payloads from Scalekit.
503
+ *
504
+ * Use this method to validate HTTP interceptor requests from Scalekit by verifying the HMAC signature.
505
+ * This ensures the interceptor payload was sent by Scalekit and hasn't been tampered with. The method
506
+ * checks the signature and timestamp to prevent replay attacks (5-minute tolerance window)
507
+ *
508
+ * @param {string} secret Your interceptor signing secret that you can copy from Scalekit Dashboard
509
+ * @param {Record<string, string>} headers The HTTP headers from the interceptor request
510
+ * @param {string} payload The raw interceptor request body as a string
511
+ * @return {boolean} Returns true if the interceptor payload is valid.
276
512
  */
277
- verifyInterceptorPayload(secret: string, headers: Record<string, string>, payload: string): boolean {
278
- const interceptorId = headers['interceptor-id'];
279
- const interceptorTimestamp = headers['interceptor-timestamp'];
280
- const interceptorSignature = headers['interceptor-signature'];
281
-
282
- return this.verifyPayloadSignature(secret, interceptorId, interceptorTimestamp, interceptorSignature, payload);
513
+ verifyInterceptorPayload(
514
+ secret: string,
515
+ headers: Record<string, string>,
516
+ payload: string
517
+ ): boolean {
518
+ const interceptorId = headers["interceptor-id"];
519
+ const interceptorTimestamp = headers["interceptor-timestamp"];
520
+ const interceptorSignature = headers["interceptor-signature"];
521
+
522
+ return this.verifyPayloadSignature(
523
+ secret,
524
+ interceptorId,
525
+ interceptorTimestamp,
526
+ interceptorSignature,
527
+ payload
528
+ );
283
529
  }
284
530
 
285
531
  /**
286
532
  * Common payload signature verification logic
287
- *
533
+ *
288
534
  * @param {string} secret The secret
289
535
  * @param {string} id The webhook/interceptor id
290
536
  * @param {string} timestamp The timestamp
@@ -292,62 +538,78 @@ export default class ScalekitClient {
292
538
  * @param {string} payload The payload
293
539
  * @return {boolean} Returns true if the payload signature is valid.
294
540
  */
295
- private verifyPayloadSignature(secret: string, id: string, timestamp: string, signature: string, payload: string): boolean {
541
+ private verifyPayloadSignature(
542
+ secret: string,
543
+ id: string,
544
+ timestamp: string,
545
+ signature: string,
546
+ payload: string
547
+ ): boolean {
296
548
  if (!id || !timestamp || !signature) {
297
549
  throw new WebhookVerificationError("Missing required headers");
298
550
  }
299
-
551
+
300
552
  const secretParts = secret.split("_");
301
553
  if (secretParts.length < 2) {
302
554
  throw new WebhookVerificationError("Invalid secret");
303
555
  }
304
-
556
+
305
557
  try {
306
558
  const timestampDate = this.verifyTimestamp(timestamp);
307
- const data = `${id}.${Math.floor(timestampDate.getTime() / 1000)}.${payload}`;
308
- const secretBytes = Buffer.from(secretParts[1], 'base64');
559
+ const data = `${id}.${Math.floor(
560
+ timestampDate.getTime() / 1000
561
+ )}.${payload}`;
562
+ const secretBytes = Buffer.from(secretParts[1], "base64");
309
563
  const computedSignature = this.computeSignature(secretBytes, data);
310
564
  const receivedSignatures = signature.split(" ");
311
-
565
+
312
566
  for (const versionedSignature of receivedSignatures) {
313
567
  const [version, receivedSignature] = versionedSignature.split(",");
314
568
  if (version !== WEBHOOK_SIGNATURE_VERSION) {
315
569
  continue;
316
570
  }
317
- if (crypto.timingSafeEqual(Buffer.from(receivedSignature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
571
+ if (
572
+ crypto.timingSafeEqual(
573
+ Buffer.from(receivedSignature, "base64"),
574
+ Buffer.from(computedSignature, "base64")
575
+ )
576
+ ) {
318
577
  return true;
319
578
  }
320
579
  }
321
580
 
322
- throw new WebhookVerificationError("Invalid signature");
581
+ throw new WebhookVerificationError("Invalid Signature");
323
582
  } catch (error) {
324
583
  if (error instanceof WebhookVerificationError) {
325
584
  throw error;
326
585
  }
327
- throw new WebhookVerificationError("Invalid signature");
586
+ throw new WebhookVerificationError("Invalid Signature");
328
587
  }
329
588
  }
330
589
 
331
590
  /**
332
- * Validates a token and returns its payload if valid.
591
+ * Validates a token and returns the claims as json payload if valid.
333
592
  * Supports issuer, audience, and scope validation.
334
- *
593
+ *
335
594
  * @param {string} token The token to be validated
336
595
  * @param {TokenValidationOptions} options Optional validation options for issuer, audience, and scopes
337
596
  * @return {Promise<T>} Returns the token payload if valid
338
597
  * @throws {ScalekitValidateTokenFailureException} If token is invalid or missing required scopes
339
598
  */
340
- async validateToken<T>(token: string, options?: TokenValidationOptions): Promise<T> {
599
+ async validateToken<T>(
600
+ token: string,
601
+ options?: TokenValidationOptions
602
+ ): Promise<T> {
341
603
  await this.coreClient.getJwks();
342
604
  const jwks = jose.createLocalJWKSet({
343
- keys: this.coreClient.keys
344
- })
605
+ keys: this.coreClient.keys,
606
+ });
345
607
  try {
346
608
  const { payload } = await jose.jwtVerify<T>(token, jwks, {
347
609
  ...(options?.issuer && { issuer: options.issuer }),
348
- ...(options?.audience && { audience: options.audience })
610
+ ...(options?.audience && { audience: options.audience }),
349
611
  });
350
-
612
+
351
613
  if (options?.requiredScopes && options.requiredScopes.length > 0) {
352
614
  this.verifyScopes(token, options.requiredScopes);
353
615
  }
@@ -360,7 +622,7 @@ export default class ScalekitClient {
360
622
 
361
623
  /**
362
624
  * Verify that the token contains the required scopes
363
- *
625
+ *
364
626
  * @param {string} token The token to verify
365
627
  * @param {string[]} requiredScopes The scopes that must be present in the token
366
628
  * @return {boolean} Returns true if all required scopes are present
@@ -369,19 +631,23 @@ export default class ScalekitClient {
369
631
  verifyScopes(token: string, requiredScopes: string[]): boolean {
370
632
  const payload = jose.decodeJwt(token);
371
633
  const scopes = this.extractScopesFromPayload(payload);
372
-
373
- const missingScopes = requiredScopes.filter(scope => !scopes.includes(scope));
374
-
634
+
635
+ const missingScopes = requiredScopes.filter(
636
+ (scope) => !scopes.includes(scope)
637
+ );
638
+
375
639
  if (missingScopes.length > 0) {
376
- throw new ScalekitValidateTokenFailureException(`Token missing required scopes: ${missingScopes.join(', ')}`);
640
+ throw new ScalekitValidateTokenFailureException(
641
+ `Token missing required scopes: ${missingScopes.join(", ")}`
642
+ );
377
643
  }
378
-
644
+
379
645
  return true;
380
646
  }
381
647
 
382
648
  /**
383
649
  * Extract scopes from token payload
384
- *
650
+ *
385
651
  * @param {any} payload The token payload
386
652
  * @return {string[]} Array of scopes found in the token
387
653
  */
@@ -394,7 +660,7 @@ export default class ScalekitClient {
394
660
 
395
661
  /**
396
662
  * Verify the timestamp
397
- *
663
+ *
398
664
  * @param {string} timestampStr The timestamp string
399
665
  * @return {Date} Returns the timestamp
400
666
  */
@@ -416,36 +682,100 @@ export default class ScalekitClient {
416
682
 
417
683
  /**
418
684
  * Compute the signature
419
- *
685
+ *
420
686
  * @param {Buffer} secretBytes The secret bytes
421
687
  * @param {string} data The data to be signed
422
688
  * @return {string} Returns the signature
423
689
  */
424
690
  private computeSignature(secretBytes: Buffer, data: string): string {
425
- return crypto.createHmac('sha256', secretBytes).update(data).digest('base64');
691
+ return crypto
692
+ .createHmac("sha256", secretBytes)
693
+ .update(data)
694
+ .digest("base64");
426
695
  }
427
696
 
428
697
  /**
429
- * Refresh access token using a refresh token
430
- * @param {string} refreshToken The refresh token to use
431
- * @returns {Promise<RefreshTokenResponse>} Returns new access token, refresh token and other details
432
- * @throws {Error} When authentication fails or response data is invalid
698
+ * Obtains a new access token using a refresh token.
699
+ *
700
+ * Use this method to get a new access token when the current one expires, without requiring
701
+ * the user to re-authenticate. This implements the OAuth 2.0 refresh token grant type.
702
+ * The method returns both a new access token and a new refresh token (token rotation).
703
+ *
704
+ * @param {string} refreshToken - The refresh token obtained from a previous authentication
705
+ *
706
+ * @returns {Promise<RefreshTokenResponse>} Response containing:
707
+ * - accessToken: New access token for API authorization
708
+ * - refreshToken: New refresh token (the old one is invalidated)
709
+ *
710
+ * @throws {Error} When the refresh token is missing
711
+ * @throws {Error} When the refresh token is invalid, expired, or revoked
712
+ * @throws {Error} When the authentication server response is invalid
713
+ *
714
+ * @example
715
+ * // Refresh tokens before they expire
716
+ * async function refreshUserToken(userId) {
717
+ * try {
718
+ * const oldRefreshToken = await getStoredRefreshToken(userId);
719
+ *
720
+ * const result = await scalekitClient.refreshAccessToken(oldRefreshToken);
721
+ *
722
+ * // Store the new tokens (old refresh token is now invalid)
723
+ * await storeTokens(userId, {
724
+ * accessToken: result.accessToken,
725
+ * refreshToken: result.refreshToken
726
+ * });
727
+ *
728
+ * return result.accessToken;
729
+ * } catch (error) {
730
+ * console.error('Token refresh failed:', error);
731
+ * // Redirect user to login
732
+ * throw new Error('Please log in again');
733
+ * }
734
+ * }
735
+ *
736
+ * @example
737
+ * // Automatic token refresh middleware
738
+ * app.use(async (req, res, next) => {
739
+ * const accessToken = req.session.accessToken;
740
+ * const refreshToken = req.session.refreshToken;
741
+ *
742
+ * // Check if access token is expired (decode JWT and check exp claim)
743
+ * if (isTokenExpired(accessToken) && refreshToken) {
744
+ * try {
745
+ * const result = await scalekitClient.refreshAccessToken(refreshToken);
746
+ * req.session.accessToken = result.accessToken;
747
+ * req.session.refreshToken = result.refreshToken;
748
+ * } catch (error) {
749
+ * return res.redirect('/login');
750
+ * }
751
+ * }
752
+ * next();
753
+ * });
754
+ *
433
755
  */
434
- async refreshAccessToken(refreshToken: string): Promise<RefreshTokenResponse> {
756
+ async refreshAccessToken(
757
+ refreshToken: string
758
+ ): Promise<RefreshTokenResponse> {
435
759
  if (!refreshToken) {
436
760
  throw new Error("Refresh token is required");
437
761
  }
438
762
 
439
763
  let res;
440
764
  try {
441
- res = await this.coreClient.authenticate(QueryString.stringify({
442
- grant_type: GrantType.RefreshToken,
443
- client_id: this.coreClient.clientId,
444
- client_secret: this.coreClient.clientSecret,
445
- refresh_token: refreshToken
446
- }));
765
+ res = await this.coreClient.authenticate(
766
+ QueryString.stringify({
767
+ grant_type: GrantType.RefreshToken,
768
+ client_id: this.coreClient.clientId,
769
+ client_secret: this.coreClient.clientSecret,
770
+ refresh_token: refreshToken,
771
+ })
772
+ );
447
773
  } catch (error) {
448
- throw new Error(`Failed to refresh token: ${error instanceof Error ? error.message : 'Unknown error'}`);
774
+ throw new Error(
775
+ `Failed to refresh token: ${
776
+ error instanceof Error ? error.message : "Unknown error"
777
+ }`
778
+ );
449
779
  }
450
780
 
451
781
  if (!res || !res.data) {
@@ -464,8 +794,7 @@ export default class ScalekitClient {
464
794
 
465
795
  return {
466
796
  accessToken: access_token,
467
- refreshToken: refresh_token
797
+ refreshToken: refresh_token,
468
798
  };
469
799
  }
470
800
  }
471
-