@slyxup/core 0.2.4 → 1.0.1
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/.turbo/turbo-build.log +4 -0
- package/LICENSE +21 -0
- package/README.md +35 -8
- package/dist/client.d.ts +119 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -10
- package/dist/client.js.map +1 -1
- package/dist/types.d.ts +50 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/client.ts +397 -19
- package/src/types.ts +58 -2
package/dist/types.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export interface SlyxupUser {
|
|
|
3
3
|
projectId: string | null;
|
|
4
4
|
email: string;
|
|
5
5
|
emailVerified: boolean;
|
|
6
|
+
username: string | null;
|
|
6
7
|
firstName: string | null;
|
|
7
8
|
lastName: string | null;
|
|
8
9
|
avatarUrl: string | null;
|
|
10
|
+
twoFactorEnabled: boolean;
|
|
9
11
|
preferences: Record<string, unknown> | null;
|
|
10
12
|
createdAt: string;
|
|
11
13
|
updatedAt: string;
|
|
@@ -16,18 +18,21 @@ export interface SlyxupSession {
|
|
|
16
18
|
expiresAt: string;
|
|
17
19
|
}
|
|
18
20
|
export interface SignInInput {
|
|
19
|
-
email
|
|
21
|
+
email?: string;
|
|
22
|
+
username?: string;
|
|
20
23
|
password: string;
|
|
21
24
|
projectId?: string;
|
|
22
25
|
}
|
|
23
|
-
export interface SignUpInput extends SignInInput {
|
|
26
|
+
export interface SignUpInput extends Omit<SignInInput, 'username'> {
|
|
24
27
|
firstName?: string;
|
|
25
28
|
lastName?: string;
|
|
29
|
+
username?: string;
|
|
26
30
|
}
|
|
27
31
|
export interface UpdateUserInput {
|
|
28
32
|
firstName?: string;
|
|
29
33
|
lastName?: string;
|
|
30
34
|
avatarUrl?: string;
|
|
35
|
+
username?: string | null;
|
|
31
36
|
preferences?: Record<string, unknown>;
|
|
32
37
|
}
|
|
33
38
|
export interface SessionResponse {
|
|
@@ -51,6 +56,9 @@ export interface SlyxupSessionInfo {
|
|
|
51
56
|
export interface SessionsResponse {
|
|
52
57
|
ok: true;
|
|
53
58
|
sessions: SlyxupSessionInfo[];
|
|
59
|
+
total: number;
|
|
60
|
+
limit: number;
|
|
61
|
+
offset: number;
|
|
54
62
|
}
|
|
55
63
|
export interface RevokeSessionsResponse {
|
|
56
64
|
ok: true;
|
|
@@ -66,6 +74,44 @@ export interface AuthResponse {
|
|
|
66
74
|
sessionToken?: string;
|
|
67
75
|
expiresAt?: string;
|
|
68
76
|
}
|
|
77
|
+
/** Returned by signIn when the account has 2FA enabled. */
|
|
78
|
+
export interface TwoFactorRequiredResponse {
|
|
79
|
+
ok: false;
|
|
80
|
+
code: '2FA_REQUIRED';
|
|
81
|
+
challengeToken: string;
|
|
82
|
+
}
|
|
83
|
+
/** Union returned by client.auth.signIn. */
|
|
84
|
+
export type SignInResponse = AuthResponse | TwoFactorRequiredResponse;
|
|
85
|
+
export interface TOTPSetupResponse {
|
|
86
|
+
ok: true;
|
|
87
|
+
secret: string;
|
|
88
|
+
provisioningUri: string;
|
|
89
|
+
accountName: string;
|
|
90
|
+
}
|
|
91
|
+
export interface EnableTOTPResponse {
|
|
92
|
+
ok: true;
|
|
93
|
+
recoveryCodes: string[];
|
|
94
|
+
}
|
|
95
|
+
export interface TwoFactorStatusResponse {
|
|
96
|
+
ok: boolean;
|
|
97
|
+
enabled: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface ConnectedAccount {
|
|
100
|
+
id: string;
|
|
101
|
+
provider: 'google' | 'github';
|
|
102
|
+
providerAccountId: string;
|
|
103
|
+
createdAt: string;
|
|
104
|
+
}
|
|
105
|
+
export interface ConnectedAccountsResponse {
|
|
106
|
+
ok: true;
|
|
107
|
+
accounts: ConnectedAccount[];
|
|
108
|
+
}
|
|
109
|
+
/** Input for completing a 2FA sign-in challenge. */
|
|
110
|
+
export interface CompleteSignInInput {
|
|
111
|
+
challengeToken: string;
|
|
112
|
+
code?: string;
|
|
113
|
+
recoveryCode?: string;
|
|
114
|
+
}
|
|
69
115
|
export interface ErrorResponse {
|
|
70
116
|
ok: false;
|
|
71
117
|
error: string;
|
|
@@ -74,6 +120,8 @@ export type Result<T> = T | ErrorResponse;
|
|
|
74
120
|
export interface SlyxupClientOptions {
|
|
75
121
|
/** Publishable key — pk_test_xxx / pk_live_xxx */
|
|
76
122
|
publishableKey?: string;
|
|
123
|
+
/** Secret key — sk_test_xxx / sk_live_xxx (server-side admin API) */
|
|
124
|
+
secretKey?: string;
|
|
77
125
|
/** API base URL, default https://auth.slyxup.online */
|
|
78
126
|
apiUrl?: string;
|
|
79
127
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slyxup/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "@slyxup/core — SlyxUp SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "tsc -p tsconfig.json",
|
|
10
|
-
"typecheck": "tsc --noEmit",
|
|
11
|
-
"test": "vitest run --passWithNoTests"
|
|
12
|
-
},
|
|
13
8
|
"repository": {
|
|
14
9
|
"type": "git",
|
|
15
10
|
"url": "git+https://github.com/slyxup/stack.git",
|
|
@@ -26,5 +21,10 @@
|
|
|
26
21
|
},
|
|
27
22
|
"devDependencies": {
|
|
28
23
|
"vitest": "^4.1.11"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run --passWithNoTests"
|
|
29
29
|
}
|
|
30
|
-
}
|
|
30
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -7,14 +7,21 @@ import {
|
|
|
7
7
|
import type {
|
|
8
8
|
AuthResponse,
|
|
9
9
|
ChangePasswordInput,
|
|
10
|
+
CompleteSignInInput,
|
|
11
|
+
ConnectedAccountsResponse,
|
|
12
|
+
EnableTOTPResponse,
|
|
10
13
|
ErrorResponse,
|
|
11
14
|
Result,
|
|
12
15
|
RevokeSessionsResponse,
|
|
13
16
|
SessionResponse,
|
|
14
17
|
SessionsResponse,
|
|
15
18
|
SignInInput,
|
|
19
|
+
SignInResponse,
|
|
16
20
|
SignUpInput,
|
|
17
21
|
SlyxupClientOptions,
|
|
22
|
+
TOTPSetupResponse,
|
|
23
|
+
TwoFactorRequiredResponse,
|
|
24
|
+
TwoFactorStatusResponse,
|
|
18
25
|
UpdateUserInput,
|
|
19
26
|
UserResponse,
|
|
20
27
|
} from './types.js';
|
|
@@ -49,20 +56,32 @@ function createCookieJar() {
|
|
|
49
56
|
*/
|
|
50
57
|
export class SlyxupClient {
|
|
51
58
|
readonly publishableKey?: string;
|
|
59
|
+
readonly secretKey?: string;
|
|
52
60
|
readonly apiUrl: string;
|
|
53
61
|
private _getToken?: () => string | undefined;
|
|
54
62
|
private _request?: <T>(path: string, init?: RequestInit) => Promise<T>;
|
|
55
63
|
|
|
56
64
|
readonly auth: {
|
|
57
65
|
signUp: (input: SignUpInput) => Promise<AuthResponse>;
|
|
58
|
-
signIn: (input: SignInInput) => Promise<
|
|
66
|
+
signIn: (input: SignInInput) => Promise<SignInResponse>;
|
|
59
67
|
signOut: () => Promise<{ ok: true }>;
|
|
60
68
|
resendVerification: (email: string) => Promise<{ ok: true }>;
|
|
69
|
+
forgotPassword: (
|
|
70
|
+
email: string,
|
|
71
|
+
projectId?: string
|
|
72
|
+
) => Promise<{ ok: true }>;
|
|
73
|
+
resetPassword: (token: string, password: string) => Promise<{ ok: true }>;
|
|
74
|
+
verifyEmail: (token: string) => Promise<{ ok: true }>;
|
|
75
|
+
/** Complete a 2FA challenge returned by signIn. */
|
|
76
|
+
completeSignIn: (input: CompleteSignInInput) => Promise<AuthResponse>;
|
|
61
77
|
};
|
|
62
78
|
|
|
63
79
|
readonly sessions: {
|
|
64
80
|
get: () => Promise<SessionResponse>;
|
|
65
|
-
list: (
|
|
81
|
+
list: (opts?: {
|
|
82
|
+
limit?: number;
|
|
83
|
+
offset?: number;
|
|
84
|
+
}) => Promise<SessionsResponse>;
|
|
66
85
|
revoke: (sessionId: string) => Promise<{ ok: true }>;
|
|
67
86
|
revokeOthers: () => Promise<RevokeSessionsResponse>;
|
|
68
87
|
};
|
|
@@ -71,12 +90,100 @@ export class SlyxupClient {
|
|
|
71
90
|
change: (input: ChangePasswordInput) => Promise<{ ok: true }>;
|
|
72
91
|
};
|
|
73
92
|
|
|
93
|
+
readonly twoFactor: {
|
|
94
|
+
setup: () => Promise<TOTPSetupResponse>;
|
|
95
|
+
status: () => Promise<TwoFactorStatusResponse>;
|
|
96
|
+
enable: (secret: string, code: string) => Promise<EnableTOTPResponse>;
|
|
97
|
+
verify: (code: string) => Promise<{ ok: boolean; valid: boolean }>;
|
|
98
|
+
disable: (code: string) => Promise<{ ok: true }>;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
readonly accounts: {
|
|
102
|
+
list: () => Promise<ConnectedAccountsResponse>;
|
|
103
|
+
unlink: (
|
|
104
|
+
accountId: string,
|
|
105
|
+
provider: 'google' | 'github'
|
|
106
|
+
) => Promise<{ ok: true }>;
|
|
107
|
+
};
|
|
108
|
+
|
|
74
109
|
readonly users: {
|
|
75
110
|
me: () => Promise<UserResponse>;
|
|
76
111
|
update: (input: UpdateUserInput) => Promise<UserResponse>;
|
|
77
112
|
delete: () => Promise<{ ok: true }>;
|
|
78
113
|
};
|
|
79
114
|
|
|
115
|
+
readonly admin: {
|
|
116
|
+
/** Get current project details */
|
|
117
|
+
getProject: () => Promise<{
|
|
118
|
+
ok: true;
|
|
119
|
+
project: { id: string; name: string; createdAt: string };
|
|
120
|
+
}>;
|
|
121
|
+
/** List users in the project */
|
|
122
|
+
listUsers: (opts?: { limit?: number; offset?: number }) => Promise<{
|
|
123
|
+
ok: true;
|
|
124
|
+
users: Array<{
|
|
125
|
+
id: string;
|
|
126
|
+
email: string;
|
|
127
|
+
firstName: string | null;
|
|
128
|
+
lastName: string | null;
|
|
129
|
+
emailVerified: boolean;
|
|
130
|
+
blocked: boolean;
|
|
131
|
+
createdAt: string;
|
|
132
|
+
}>;
|
|
133
|
+
total: number;
|
|
134
|
+
}>;
|
|
135
|
+
/** Get a single user by ID */
|
|
136
|
+
getUser: (
|
|
137
|
+
userId: string
|
|
138
|
+
) => Promise<{
|
|
139
|
+
ok: true;
|
|
140
|
+
user: {
|
|
141
|
+
id: string;
|
|
142
|
+
email: string;
|
|
143
|
+
firstName: string | null;
|
|
144
|
+
lastName: string | null;
|
|
145
|
+
emailVerified: boolean;
|
|
146
|
+
blocked: boolean;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
updatedAt: string;
|
|
149
|
+
};
|
|
150
|
+
}>;
|
|
151
|
+
/** List active sessions */
|
|
152
|
+
listSessions: () => Promise<{
|
|
153
|
+
ok: true;
|
|
154
|
+
sessions: Array<{
|
|
155
|
+
id: string;
|
|
156
|
+
userId: string;
|
|
157
|
+
ipAddress: string | null;
|
|
158
|
+
userAgent: string | null;
|
|
159
|
+
expiresAt: string;
|
|
160
|
+
isExpired: boolean;
|
|
161
|
+
createdAt: string;
|
|
162
|
+
}>;
|
|
163
|
+
}>;
|
|
164
|
+
/** List API keys */
|
|
165
|
+
listKeys: () => Promise<{
|
|
166
|
+
ok: true;
|
|
167
|
+
keys: Array<{
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
prefix: string;
|
|
171
|
+
environment: string;
|
|
172
|
+
type: string;
|
|
173
|
+
lastUsedAt: string | null;
|
|
174
|
+
createdAt: string;
|
|
175
|
+
}>;
|
|
176
|
+
}>;
|
|
177
|
+
/** Create an API key (full key returned only once) */
|
|
178
|
+
createKey: (input: {
|
|
179
|
+
name: string;
|
|
180
|
+
type: 'publishable' | 'secret';
|
|
181
|
+
environment: 'test' | 'live';
|
|
182
|
+
}) => Promise<{ ok: true; id: string; key: string; prefix: string }>;
|
|
183
|
+
/** Revoke an API key */
|
|
184
|
+
revokeKey: (keyId: string) => Promise<{ ok: true }>;
|
|
185
|
+
};
|
|
186
|
+
|
|
80
187
|
/** Get current session token (for custom project APIs that need Bearer) */
|
|
81
188
|
getToken(): string | undefined {
|
|
82
189
|
return this._getToken?.();
|
|
@@ -89,15 +196,33 @@ export class SlyxupClient {
|
|
|
89
196
|
|
|
90
197
|
constructor(options: SlyxupClientOptions = {}) {
|
|
91
198
|
const jar = createCookieJar();
|
|
199
|
+
// Persist token in localStorage so refresh keeps session for cross-origin (auth -> billing) and for dashboard project APIs
|
|
200
|
+
const STORAGE_KEY = 'slyxup_session_token';
|
|
92
201
|
let storedToken: string | undefined;
|
|
202
|
+
try {
|
|
203
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
204
|
+
storedToken = window.localStorage.getItem(STORAGE_KEY) ?? undefined;
|
|
205
|
+
}
|
|
206
|
+
} catch {}
|
|
207
|
+
const persistToken = (t: string | undefined) => {
|
|
208
|
+
storedToken = t;
|
|
209
|
+
try {
|
|
210
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
211
|
+
if (t) window.localStorage.setItem(STORAGE_KEY, t);
|
|
212
|
+
else window.localStorage.removeItem(STORAGE_KEY);
|
|
213
|
+
}
|
|
214
|
+
} catch {}
|
|
215
|
+
};
|
|
93
216
|
this.publishableKey = options.publishableKey;
|
|
217
|
+
this.secretKey = options.secretKey;
|
|
94
218
|
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
|
|
95
219
|
this._getToken = () => storedToken;
|
|
96
220
|
// _request will be assigned after `request` is defined below
|
|
97
221
|
|
|
98
222
|
const requestInner = async <T>(
|
|
99
223
|
path: string,
|
|
100
|
-
init: RequestInit & { body?: string } = {}
|
|
224
|
+
init: RequestInit & { body?: string } = {},
|
|
225
|
+
opts?: { captureError?: boolean }
|
|
101
226
|
): Promise<Result<T>> => {
|
|
102
227
|
let res: Response;
|
|
103
228
|
try {
|
|
@@ -134,6 +259,7 @@ export class SlyxupClient {
|
|
|
134
259
|
.catch(() => ({ ok: false, error: 'Invalid response' }))) as object;
|
|
135
260
|
|
|
136
261
|
if (!res.ok) {
|
|
262
|
+
if (opts?.captureError) return data as Result<T>;
|
|
137
263
|
if (res.status === 401)
|
|
138
264
|
throw new UnauthorizedError(
|
|
139
265
|
'error' in data ? String(data.error) : undefined
|
|
@@ -151,30 +277,53 @@ export class SlyxupClient {
|
|
|
151
277
|
return data as Result<T>;
|
|
152
278
|
};
|
|
153
279
|
|
|
154
|
-
const post = <T>(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
280
|
+
const post = <T>(
|
|
281
|
+
path: string,
|
|
282
|
+
body?: unknown,
|
|
283
|
+
opts?: { captureError?: boolean }
|
|
284
|
+
) =>
|
|
285
|
+
requestInner<T>(
|
|
286
|
+
path,
|
|
287
|
+
{
|
|
288
|
+
method: 'POST',
|
|
289
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
290
|
+
},
|
|
291
|
+
opts
|
|
292
|
+
);
|
|
159
293
|
|
|
160
294
|
this.auth = {
|
|
161
295
|
signUp: async (input) => {
|
|
162
296
|
const res = await post<AuthResponse>('/v1/auth/sign-up', input);
|
|
163
297
|
if (!('user' in res))
|
|
164
298
|
throw new SlyxupError(res.error, 400, 'api_error');
|
|
165
|
-
if (res.sessionToken)
|
|
299
|
+
if (res.sessionToken) persistToken(res.sessionToken);
|
|
166
300
|
return res;
|
|
167
301
|
},
|
|
168
302
|
signIn: async (input) => {
|
|
169
|
-
|
|
303
|
+
// The server may answer 403 with code 2FA_REQUIRED — we must surface
|
|
304
|
+
// the challenge back to the caller instead of throwing a generic error.
|
|
305
|
+
const res = await post<SignInResponse>('/v1/auth/sign-in', input, {
|
|
306
|
+
captureError: true,
|
|
307
|
+
});
|
|
308
|
+
if ('challengeToken' in res) {
|
|
309
|
+
return res as TwoFactorRequiredResponse;
|
|
310
|
+
}
|
|
170
311
|
if (!('user' in res))
|
|
171
|
-
throw new
|
|
172
|
-
|
|
312
|
+
throw new UnauthorizedError(
|
|
313
|
+
(res as { error?: string }).error ?? 'Sign in failed'
|
|
314
|
+
);
|
|
315
|
+
if (res.sessionToken) persistToken(res.sessionToken);
|
|
316
|
+
return res;
|
|
317
|
+
},
|
|
318
|
+
completeSignIn: async (input) => {
|
|
319
|
+
const res = await post<AuthResponse>('/v1/auth/sign-in/2fa', input);
|
|
320
|
+
if (!('user' in res)) throw new UnauthorizedError(res.error);
|
|
321
|
+
if (res.sessionToken) persistToken(res.sessionToken);
|
|
173
322
|
return res;
|
|
174
323
|
},
|
|
175
324
|
signOut: async () => {
|
|
176
325
|
const res = await post<{ ok: true }>('/v1/auth/sign-out');
|
|
177
|
-
|
|
326
|
+
persistToken(undefined);
|
|
178
327
|
jar.clear();
|
|
179
328
|
return res as { ok: true };
|
|
180
329
|
},
|
|
@@ -185,6 +334,35 @@ export class SlyxupClient {
|
|
|
185
334
|
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
186
335
|
return res as { ok: true };
|
|
187
336
|
},
|
|
337
|
+
forgotPassword: async (email: string, projectId?: string) => {
|
|
338
|
+
const res = await post<{ ok: true }>(
|
|
339
|
+
'/v1/verification/password/forgot',
|
|
340
|
+
{
|
|
341
|
+
email,
|
|
342
|
+
projectId,
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
346
|
+
return res as { ok: true };
|
|
347
|
+
},
|
|
348
|
+
resetPassword: async (token: string, password: string) => {
|
|
349
|
+
const res = await post<{ ok: true }>(
|
|
350
|
+
'/v1/verification/password/reset',
|
|
351
|
+
{
|
|
352
|
+
token,
|
|
353
|
+
password,
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
357
|
+
return res as { ok: true };
|
|
358
|
+
},
|
|
359
|
+
verifyEmail: async (token: string) => {
|
|
360
|
+
const res = await post<{ ok: true }>('/v1/verification/verify', {
|
|
361
|
+
token,
|
|
362
|
+
});
|
|
363
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
364
|
+
return res as { ok: true };
|
|
365
|
+
},
|
|
188
366
|
};
|
|
189
367
|
|
|
190
368
|
this.sessions = {
|
|
@@ -193,16 +371,26 @@ export class SlyxupClient {
|
|
|
193
371
|
if (!('session' in res)) throw new UnauthorizedError(res.error);
|
|
194
372
|
return res;
|
|
195
373
|
},
|
|
196
|
-
list: async () => {
|
|
197
|
-
const
|
|
374
|
+
list: async (opts?: { limit?: number; offset?: number }) => {
|
|
375
|
+
const params = new URLSearchParams();
|
|
376
|
+
if (opts?.limit !== undefined) params.set('limit', String(opts.limit));
|
|
377
|
+
if (opts?.offset !== undefined)
|
|
378
|
+
params.set('offset', String(opts.offset));
|
|
379
|
+
const qs = params.toString();
|
|
380
|
+
const res = await requestInner<SessionsResponse>(
|
|
381
|
+
`/v1/sessions${qs ? `?${qs}` : ''}`
|
|
382
|
+
);
|
|
198
383
|
if (!('sessions' in res))
|
|
199
384
|
throw new SlyxupError(res.error, 400, 'api_error');
|
|
200
385
|
return res;
|
|
201
386
|
},
|
|
202
387
|
revoke: async (sessionId: string) => {
|
|
203
|
-
const res = await requestInner<{ ok: true }>(
|
|
204
|
-
|
|
205
|
-
|
|
388
|
+
const res = await requestInner<{ ok: true }>(
|
|
389
|
+
`/v1/sessions/${sessionId}`,
|
|
390
|
+
{
|
|
391
|
+
method: 'DELETE',
|
|
392
|
+
}
|
|
393
|
+
);
|
|
206
394
|
return res as { ok: true };
|
|
207
395
|
},
|
|
208
396
|
revokeOthers: async () => {
|
|
@@ -223,6 +411,65 @@ export class SlyxupClient {
|
|
|
223
411
|
},
|
|
224
412
|
};
|
|
225
413
|
|
|
414
|
+
this.twoFactor = {
|
|
415
|
+
setup: async () => {
|
|
416
|
+
const res = await requestInner<TOTPSetupResponse>('/v1/user/2fa/setup');
|
|
417
|
+
if (!('secret' in res))
|
|
418
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
419
|
+
return res;
|
|
420
|
+
},
|
|
421
|
+
status: async () => {
|
|
422
|
+
const res = await requestInner<TwoFactorStatusResponse>(
|
|
423
|
+
'/v1/user/2fa/status'
|
|
424
|
+
);
|
|
425
|
+
return res as TwoFactorStatusResponse;
|
|
426
|
+
},
|
|
427
|
+
enable: async (secret, code) => {
|
|
428
|
+
const res = await post<EnableTOTPResponse>(
|
|
429
|
+
'/v1/user/2fa/enable',
|
|
430
|
+
{ secret, code },
|
|
431
|
+
{ captureError: true }
|
|
432
|
+
);
|
|
433
|
+
if (!('recoveryCodes' in res))
|
|
434
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
435
|
+
return res;
|
|
436
|
+
},
|
|
437
|
+
verify: async (code) => {
|
|
438
|
+
const res = await post<{ ok: boolean; valid: boolean }>(
|
|
439
|
+
'/v1/user/2fa/verify',
|
|
440
|
+
{ code },
|
|
441
|
+
{ captureError: true }
|
|
442
|
+
);
|
|
443
|
+
if (!('valid' in res))
|
|
444
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
445
|
+
return res as { ok: boolean; valid: boolean };
|
|
446
|
+
},
|
|
447
|
+
disable: async (code) => {
|
|
448
|
+
const res = await post<{ ok: true }>('/v1/user/2fa/disable', { code });
|
|
449
|
+
if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
|
|
450
|
+
return res as { ok: true };
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
this.accounts = {
|
|
455
|
+
list: async () => {
|
|
456
|
+
const res =
|
|
457
|
+
await requestInner<ConnectedAccountsResponse>('/v1/user/accounts');
|
|
458
|
+
if (!('accounts' in res))
|
|
459
|
+
throw new SlyxupError(res.error, 400, 'api_error');
|
|
460
|
+
return res;
|
|
461
|
+
},
|
|
462
|
+
unlink: async (accountId, provider) => {
|
|
463
|
+
const res = await requestInner<{ ok: true }>(
|
|
464
|
+
`/v1/user/accounts/${accountId}?provider=${encodeURIComponent(provider)}`,
|
|
465
|
+
{
|
|
466
|
+
method: 'DELETE',
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
return res as { ok: true };
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
|
|
226
473
|
this.users = {
|
|
227
474
|
me: async () => {
|
|
228
475
|
const res = await requestInner<UserResponse>('/v1/user');
|
|
@@ -246,7 +493,138 @@ export class SlyxupClient {
|
|
|
246
493
|
},
|
|
247
494
|
};
|
|
248
495
|
|
|
249
|
-
|
|
496
|
+
// ── Admin API (requires secretKey) ──
|
|
497
|
+
const sk = options.secretKey;
|
|
498
|
+
const adminRequest = async <T>(
|
|
499
|
+
path: string,
|
|
500
|
+
init: RequestInit = {}
|
|
501
|
+
): Promise<T> => {
|
|
502
|
+
if (!sk)
|
|
503
|
+
throw new SlyxupError(
|
|
504
|
+
'Secret key required for admin API',
|
|
505
|
+
400,
|
|
506
|
+
'api_error'
|
|
507
|
+
);
|
|
508
|
+
const res = await fetch(`${this.apiUrl}${path}`, {
|
|
509
|
+
...init,
|
|
510
|
+
headers: {
|
|
511
|
+
'Content-Type': 'application/json',
|
|
512
|
+
Authorization: `Bearer ${sk}`,
|
|
513
|
+
...init.headers,
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
const data = (await res
|
|
517
|
+
.json()
|
|
518
|
+
.catch(() => ({ ok: false, error: 'Invalid response' }))) as object;
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
throw new SlyxupError(
|
|
521
|
+
'error' in data
|
|
522
|
+
? String((data as { error: unknown }).error)
|
|
523
|
+
: `Request failed (${res.status})`,
|
|
524
|
+
res.status,
|
|
525
|
+
'api_error'
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return data as T;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
this.admin = {
|
|
532
|
+
getProject: async () => {
|
|
533
|
+
const res = await adminRequest<{
|
|
534
|
+
ok: true;
|
|
535
|
+
project: { id: string; name: string; createdAt: string };
|
|
536
|
+
}>('/v1/admin/project');
|
|
537
|
+
return res;
|
|
538
|
+
},
|
|
539
|
+
listUsers: async (opts) => {
|
|
540
|
+
const params = new URLSearchParams();
|
|
541
|
+
if (opts?.limit !== undefined) params.set('limit', String(opts.limit));
|
|
542
|
+
if (opts?.offset !== undefined)
|
|
543
|
+
params.set('offset', String(opts.offset));
|
|
544
|
+
const qs = params.toString();
|
|
545
|
+
const res = await adminRequest<{
|
|
546
|
+
ok: true;
|
|
547
|
+
users: Array<{
|
|
548
|
+
id: string;
|
|
549
|
+
email: string;
|
|
550
|
+
firstName: string | null;
|
|
551
|
+
lastName: string | null;
|
|
552
|
+
emailVerified: boolean;
|
|
553
|
+
blocked: boolean;
|
|
554
|
+
createdAt: string;
|
|
555
|
+
}>;
|
|
556
|
+
total: number;
|
|
557
|
+
}>(`/v1/admin/users${qs ? `?${qs}` : ''}`);
|
|
558
|
+
return res;
|
|
559
|
+
},
|
|
560
|
+
getUser: async (userId) => {
|
|
561
|
+
const res = await adminRequest<{
|
|
562
|
+
ok: true;
|
|
563
|
+
user: {
|
|
564
|
+
id: string;
|
|
565
|
+
email: string;
|
|
566
|
+
firstName: string | null;
|
|
567
|
+
lastName: string | null;
|
|
568
|
+
emailVerified: boolean;
|
|
569
|
+
blocked: boolean;
|
|
570
|
+
createdAt: string;
|
|
571
|
+
updatedAt: string;
|
|
572
|
+
};
|
|
573
|
+
}>(`/v1/admin/users/${encodeURIComponent(userId)}`);
|
|
574
|
+
return res;
|
|
575
|
+
},
|
|
576
|
+
listSessions: async () => {
|
|
577
|
+
const res = await adminRequest<{
|
|
578
|
+
ok: true;
|
|
579
|
+
sessions: Array<{
|
|
580
|
+
id: string;
|
|
581
|
+
userId: string;
|
|
582
|
+
ipAddress: string | null;
|
|
583
|
+
userAgent: string | null;
|
|
584
|
+
expiresAt: string;
|
|
585
|
+
isExpired: boolean;
|
|
586
|
+
createdAt: string;
|
|
587
|
+
}>;
|
|
588
|
+
}>('/v1/admin/sessions');
|
|
589
|
+
return res;
|
|
590
|
+
},
|
|
591
|
+
listKeys: async () => {
|
|
592
|
+
const res = await adminRequest<{
|
|
593
|
+
ok: true;
|
|
594
|
+
keys: Array<{
|
|
595
|
+
id: string;
|
|
596
|
+
name: string;
|
|
597
|
+
prefix: string;
|
|
598
|
+
environment: string;
|
|
599
|
+
type: string;
|
|
600
|
+
lastUsedAt: string | null;
|
|
601
|
+
createdAt: string;
|
|
602
|
+
}>;
|
|
603
|
+
}>('/v1/admin/keys');
|
|
604
|
+
return res;
|
|
605
|
+
},
|
|
606
|
+
createKey: async (input) => {
|
|
607
|
+
const res = await adminRequest<{
|
|
608
|
+
ok: true;
|
|
609
|
+
id: string;
|
|
610
|
+
key: string;
|
|
611
|
+
prefix: string;
|
|
612
|
+
}>('/v1/admin/keys', { method: 'POST', body: JSON.stringify(input) });
|
|
613
|
+
return res;
|
|
614
|
+
},
|
|
615
|
+
revokeKey: async (keyId) => {
|
|
616
|
+
const res = await adminRequest<{ ok: true }>(
|
|
617
|
+
`/v1/admin/keys/${encodeURIComponent(keyId)}`,
|
|
618
|
+
{ method: 'DELETE' }
|
|
619
|
+
);
|
|
620
|
+
return res as { ok: true };
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
this._request = requestInner as unknown as <T>(
|
|
625
|
+
path: string,
|
|
626
|
+
init?: RequestInit
|
|
627
|
+
) => Promise<T>;
|
|
250
628
|
}
|
|
251
629
|
}
|
|
252
630
|
|