@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.
- package/buf.gen.yaml +1 -0
- package/lib/auth.d.ts +41 -9
- package/lib/auth.js +44 -12
- package/lib/auth.js.map +1 -1
- package/lib/connection.d.ts +195 -21
- package/lib/connection.js +197 -23
- package/lib/connection.js.map +1 -1
- package/lib/core.d.ts +2 -2
- package/lib/core.js +13 -12
- package/lib/core.js.map +1 -1
- package/lib/directory.d.ts +293 -40
- package/lib/directory.js +308 -44
- package/lib/directory.js.map +1 -1
- package/lib/domain.d.ts +166 -18
- package/lib/domain.js +178 -29
- package/lib/domain.js.map +1 -1
- package/lib/organization.d.ts +407 -40
- package/lib/organization.js +433 -49
- package/lib/organization.js.map +1 -1
- package/lib/permission.d.ts +179 -35
- package/lib/permission.js +190 -38
- package/lib/permission.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.d.ts +3 -3
- package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.js +2 -2
- package/lib/pkg/grpc/scalekit/v1/auth/auth_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.d.ts +16 -16
- package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.js +21 -21
- package/lib/pkg/grpc/scalekit/v1/auth/auth_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.d.ts +82 -0
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.js +90 -0
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_connect.js.map +1 -0
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.d.ts +647 -0
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.js +993 -0
- package/lib/pkg/grpc/scalekit/v1/auth/webauthn_pb.js.map +1 -0
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.d.ts +142 -0
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js +165 -1
- package/lib/pkg/grpc/scalekit/v1/commons/commons_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.d.ts +1 -10
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js +0 -9
- package/lib/pkg/grpc/scalekit/v1/connections/connections_connect.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.d.ts +28 -63
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js +9 -90
- package/lib/pkg/grpc/scalekit/v1/connections/connections_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.d.ts +6 -6
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_connect.js +5 -5
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.d.ts +19 -30
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js +22 -31
- package/lib/pkg/grpc/scalekit/v1/organizations/organizations_pb.js.map +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.d.ts +2 -2
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js +1 -1
- package/lib/pkg/grpc/scalekit/v1/users/users_pb.js.map +1 -1
- package/lib/role.d.ts +252 -56
- package/lib/role.js +262 -62
- package/lib/role.js.map +1 -1
- package/lib/scalekit.d.ts +323 -54
- package/lib/scalekit.js +354 -76
- package/lib/scalekit.js.map +1 -1
- package/lib/session.d.ts +235 -22
- package/lib/session.js +237 -24
- package/lib/session.js.map +1 -1
- package/lib/types/organization.d.ts +3 -0
- package/lib/user.d.ts +571 -53
- package/lib/user.js +598 -89
- package/lib/user.js.map +1 -1
- package/lib/webauthn.d.ts +33 -0
- package/lib/webauthn.js +80 -0
- package/lib/webauthn.js.map +1 -0
- package/package.json +2 -2
- package/src/auth.ts +53 -19
- package/src/connection.ts +237 -62
- package/src/core.ts +39 -33
- package/src/directory.ts +356 -98
- package/src/domain.ts +215 -68
- package/src/organization.ts +506 -105
- package/src/permission.ts +234 -88
- package/src/pkg/grpc/scalekit/v1/auth/auth_connect.ts +3 -3
- package/src/pkg/grpc/scalekit/v1/auth/auth_pb.ts +24 -24
- package/src/pkg/grpc/scalekit/v1/auth/webauthn_connect.ts +89 -0
- package/src/pkg/grpc/scalekit/v1/auth/webauthn_pb.ts +1263 -0
- package/src/pkg/grpc/scalekit/v1/commons/commons_pb.ts +217 -0
- package/src/pkg/grpc/scalekit/v1/connections/connections_connect.ts +1 -10
- package/src/pkg/grpc/scalekit/v1/connections/connections_pb.ts +42 -129
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_connect.ts +6 -6
- package/src/pkg/grpc/scalekit/v1/organizations/organizations_pb.ts +28 -43
- package/src/pkg/grpc/scalekit/v1/users/users_pb.ts +3 -3
- package/src/role.ts +336 -136
- package/src/scalekit.ts +478 -149
- package/src/session.ts +266 -63
- package/src/types/organization.ts +4 -0
- package/src/user.ts +675 -168
- package/src/webauthn.ts +98 -0
- 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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param {string}
|
|
111
|
-
*
|
|
112
|
-
* @param {
|
|
113
|
-
* @param {string} options.
|
|
114
|
-
* @param {string} options.
|
|
115
|
-
* @param {string} options.
|
|
116
|
-
* @param {string} options.
|
|
117
|
-
* @param {string} options.
|
|
118
|
-
* @param {string} options.
|
|
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
|
-
*
|
|
122
|
-
* const
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
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: [
|
|
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:
|
|
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 && {
|
|
213
|
+
...(options.organizationId && {
|
|
214
|
+
organization_id: options.organizationId,
|
|
215
|
+
}),
|
|
151
216
|
...(options.codeChallenge && { code_challenge: options.codeChallenge }),
|
|
152
|
-
...(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
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
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(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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(
|
|
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 && {
|
|
247
|
-
|
|
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
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
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(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
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(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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(
|
|
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(
|
|
308
|
-
|
|
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 (
|
|
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
|
|
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
|
|
586
|
+
throw new WebhookVerificationError("Invalid Signature");
|
|
328
587
|
}
|
|
329
588
|
}
|
|
330
589
|
|
|
331
590
|
/**
|
|
332
|
-
* Validates a token and returns
|
|
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>(
|
|
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(
|
|
374
|
-
|
|
634
|
+
|
|
635
|
+
const missingScopes = requiredScopes.filter(
|
|
636
|
+
(scope) => !scopes.includes(scope)
|
|
637
|
+
);
|
|
638
|
+
|
|
375
639
|
if (missingScopes.length > 0) {
|
|
376
|
-
throw new ScalekitValidateTokenFailureException(
|
|
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
|
|
691
|
+
return crypto
|
|
692
|
+
.createHmac("sha256", secretBytes)
|
|
693
|
+
.update(data)
|
|
694
|
+
.digest("base64");
|
|
426
695
|
}
|
|
427
696
|
|
|
428
697
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
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(
|
|
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(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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(
|
|
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
|
-
|