secryn 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.ts ADDED
@@ -0,0 +1,502 @@
1
+ import { logger } from "./logger.js";
2
+ import type {
3
+ LoginBody,
4
+ RegisterBody,
5
+ LoginMFAResponse,
6
+ MFAConfirmBody,
7
+ MFARecoveryBody,
8
+ MFASetupResponse,
9
+ MFAEnableBody,
10
+ MFAStatusResponse,
11
+ MFARecoveryCodesResponse,
12
+ ForgotPasswordBody,
13
+ ResetPasswordBody,
14
+ UpdateUserInput,
15
+ CreateProjectInput,
16
+ CreateSecretInput,
17
+ UpdateSecretInput,
18
+ CreateApiKeyInput,
19
+ UpdateApiKeyInput,
20
+ } from "./types.js";
21
+
22
+ interface ClientOptions {
23
+ baseUrl?: string;
24
+ apiKey?: string;
25
+ }
26
+
27
+ interface RequestOptions {
28
+ method: "GET" | "POST" | "PUT" | "DELETE";
29
+ path: string;
30
+ body?: unknown;
31
+ }
32
+
33
+ interface SecrynApiErrorBody {
34
+ success: boolean;
35
+ message: string;
36
+ code: string;
37
+ details?: unknown;
38
+ }
39
+
40
+ export class SecrynApiError extends Error {
41
+ /**
42
+ * @param message - Human-readable error description from the API.
43
+ * @param statusCode - HTTP status code returned by the server.
44
+ * @param code - Machine-readable error identifier (e.g. ``"NOT_FOUND"``).
45
+ * @param details - Optional structured context (validation errors, etc.).
46
+ */
47
+ constructor(
48
+ message: string,
49
+ public readonly statusCode: number,
50
+ public readonly code: string,
51
+ public readonly details?: unknown,
52
+ ) {
53
+ super(message);
54
+ this.name = "SecrynApiError";
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Normalise a base URL and a relative path into a single absolute URL.
60
+ *
61
+ * Strips a trailing ``/`` from the base and ensures the path starts with
62
+ * ``/`` so that the resulting URL has exactly one separator between host
63
+ * and path regardless of the caller's formatting.
64
+ *
65
+ * @param base - Base URL with optional trailing slash.
66
+ * @param path - Relative API path with optional leading slash.
67
+ * @returns Fully-qualified URL string.
68
+ */
69
+ function buildUrl(base: string, path: string): string {
70
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
71
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
72
+ return `${normalizedBase}${normalizedPath}`;
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Cookie jar — persists auth tokens across requests in Node.js.
77
+ //
78
+ // Node's native ``fetch`` does not automatically store or send cookies the
79
+ // way browsers do. This class manually parses ``Set-Cookie`` response headers
80
+ // and injects a ``Cookie`` header on subsequent requests so that cookie-based
81
+ // authentication works server-side.
82
+ // ---------------------------------------------------------------------------
83
+ class CookieJar {
84
+ private cookies = new Map<string, string>();
85
+
86
+ /**
87
+ * Extract a cookie name/value pair from a ``Set-Cookie`` header value.
88
+ * Only the first ``name=value`` segment is kept; attributes (``Path``,
89
+ * ``HttpOnly``, etc.) are ignored.
90
+ */
91
+ setFromHeader(setCookieHeader: string): void {
92
+ const parts = setCookieHeader.split(";");
93
+ const first = parts[0]?.trim();
94
+ if (!first) return;
95
+ const eq = first.indexOf("=");
96
+ if (eq === -1) return;
97
+ const name = first.slice(0, eq).trim();
98
+ const value = first.slice(eq + 1).trim();
99
+ if (value) {
100
+ this.cookies.set(name, value);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Serialise all stored cookies into a single ``Cookie`` header value.
106
+ * Returns an empty string when no cookies are stored.
107
+ */
108
+ getCookieHeader(): string {
109
+ if (this.cookies.size === 0) return "";
110
+ return Array.from(this.cookies.entries())
111
+ .map(([n, v]) => `${n}=${v}`)
112
+ .join("; ");
113
+ }
114
+
115
+ clear(): void {
116
+ this.cookies.clear();
117
+ }
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // SecrynClient — HTTP client for the full Secryn REST API.
122
+ //
123
+ // Supports two authentication modes:
124
+ // • Cookie-based — after ``auth.login``, session cookies are persisted
125
+ // across requests via an internal ``CookieJar``.
126
+ // • API-key-based — pass ``apiKey`` in the constructor options.
127
+ //
128
+ // All API resources are exposed as namespaced sub-objects (``auth``,
129
+ // ``projects``, ``secrets``, etc.) for discoverable auto-completion.
130
+ // ---------------------------------------------------------------------------
131
+ export class SecrynClient {
132
+ readonly baseUrl: string;
133
+ private readonly apiKey?: string;
134
+ private readonly cookieJar = new CookieJar();
135
+
136
+ /**
137
+ * @param options - Optional configuration.
138
+ * @param options.baseUrl - Base URL including the ``/api/v1`` prefix.
139
+ * Defaults to ``http://localhost:3000/api/v1``.
140
+ * @param options.apiKey - Optional API key for programmatic access.
141
+ */
142
+ constructor(options: ClientOptions = {}) {
143
+ this.baseUrl = options.baseUrl ?? "http://localhost:3000/api/v1";
144
+ this.apiKey = options.apiKey;
145
+ }
146
+
147
+ // ---- raw HTTP -----------------------------------------------------------
148
+
149
+ /**
150
+ * Execute an HTTP request and handle common response semantics.
151
+ *
152
+ * Key behaviours:
153
+ * - Injects stored cookies from the internal cookie jar as the
154
+ * ``Cookie`` header (enabling session-based auth).
155
+ * - Injects the ``api-key`` header when the client was configured with one.
156
+ * - Persists ``Set-Cookie`` response headers back into the cookie jar.
157
+ * - Returns ``undefined`` for 204 No Content responses.
158
+ * - Parses the body as JSON; falls back to returning the raw text on
159
+ * parse failure.
160
+ * - Throws {@link SecrynApiError} when ``response.ok`` is ``false``,
161
+ * extracting ``message``, ``code``, and ``details`` from the JSON body
162
+ * when available.
163
+ *
164
+ * @param opts - Request definition.
165
+ * @template T - Expected shape of the parsed JSON response.
166
+ * @returns Parsed JSON typed as ``T``, or ``undefined`` for 204.
167
+ * @throws {SecrynApiError} When the API responds with a non-2xx status.
168
+ */
169
+ private async request<T = unknown>(opts: RequestOptions): Promise<T> {
170
+ const url = buildUrl(this.baseUrl, opts.path);
171
+ const headers: Record<string, string> = {
172
+ "Content-Type": "application/json",
173
+ Accept: "application/json",
174
+ };
175
+
176
+ const cookieHeader = this.cookieJar.getCookieHeader();
177
+ if (cookieHeader) {
178
+ headers["Cookie"] = cookieHeader;
179
+ }
180
+ if (this.apiKey) {
181
+ headers["api-key"] = this.apiKey;
182
+ }
183
+
184
+ const init: RequestInit = {
185
+ method: opts.method,
186
+ headers,
187
+ };
188
+
189
+ if (opts.body !== undefined) {
190
+ init.body = JSON.stringify(opts.body);
191
+ }
192
+
193
+ const response = await fetch(url, init);
194
+
195
+ // Persist cookies
196
+ const setCookie = response.headers.get("set-cookie");
197
+ if (setCookie) {
198
+ this.cookieJar.setFromHeader(setCookie);
199
+ }
200
+
201
+ // 204 No Content
202
+ if (response.status === 204) {
203
+ return undefined as T;
204
+ }
205
+
206
+ const text = await response.text();
207
+ let data: unknown;
208
+ try {
209
+ data = text ? JSON.parse(text) : null;
210
+ } catch {
211
+ data = text;
212
+ }
213
+
214
+ if (!response.ok) {
215
+ const err = data as SecrynApiErrorBody | undefined;
216
+ throw new SecrynApiError(
217
+ err?.message ?? `HTTP ${response.status}`,
218
+ response.status,
219
+ err?.code ?? "UNKNOWN",
220
+ err?.details,
221
+ );
222
+ }
223
+
224
+ return data as T;
225
+ }
226
+
227
+ // ---- convenience shortcuts ----------------------------------------------
228
+
229
+ private get<T = unknown>(path: string): Promise<T> {
230
+ return this.request<T>({ method: "GET", path });
231
+ }
232
+
233
+ private post<T = unknown>(path: string, body?: unknown): Promise<T> {
234
+ return this.request<T>({ method: "POST", path, body });
235
+ }
236
+
237
+ private put<T = unknown>(path: string, body?: unknown): Promise<T> {
238
+ return this.request<T>({ method: "PUT", path, body });
239
+ }
240
+
241
+ private del<T = unknown>(path: string): Promise<T> {
242
+ return this.request<T>({ method: "DELETE", path });
243
+ }
244
+
245
+ // =====================================================================
246
+ // Auth
247
+ // =====================================================================
248
+ auth = {
249
+ login: async (email: string, password: string): Promise<{ ok: boolean } | LoginMFAResponse> => {
250
+ const body: LoginBody = { email, password };
251
+ const result = await this.post<{ ok: boolean } | LoginMFAResponse>("/auth/login", body);
252
+ // Emit an audit log on every login attempt (success or failure is
253
+ // reflected by whether this call throws).
254
+ logger.audit("SDK_LOGIN", email);
255
+ return result;
256
+ },
257
+
258
+ register: async (
259
+ email: string,
260
+ password: string,
261
+ username?: string,
262
+ ): Promise<{ ok: boolean }> => {
263
+ const body: RegisterBody = { email, password, username };
264
+ return this.post<{ ok: boolean }>("/auth/register", body);
265
+ },
266
+
267
+ /**
268
+ * Send a logout request and unconditionally clear the local cookie jar.
269
+ *
270
+ * The cookie jar is cleared inside a ``finally`` block so that even if
271
+ * the server returns an error the local session is still discarded.
272
+ */
273
+ logout: async (): Promise<void> => {
274
+ try {
275
+ await this.post("/auth/logout");
276
+ } finally {
277
+ this.cookieJar.clear();
278
+ }
279
+ },
280
+
281
+ refresh: async (): Promise<void> => {
282
+ await this.post("/auth/refresh");
283
+ },
284
+
285
+ forgotPassword: async (email: string): Promise<{ message: string }> => {
286
+ const body: ForgotPasswordBody = { email };
287
+ return this.post<{ message: string }>("/auth/forgot-password", body);
288
+ },
289
+
290
+ resetPassword: async (token: string, password: string): Promise<{ message: string }> => {
291
+ const body: ResetPasswordBody = { token, password };
292
+ return this.post<{ message: string }>("/auth/reset-password", body);
293
+ },
294
+
295
+ /**
296
+ * Check whether the client currently holds any session cookies.
297
+ * Does not validate the cookie with the server.
298
+ */
299
+ isAuthenticated: (): boolean => this.cookieJar.getCookieHeader() !== "",
300
+ };
301
+
302
+ // =====================================================================
303
+ // MFA
304
+ // =====================================================================
305
+ mfa = {
306
+ setup: (): Promise<MFASetupResponse> => this.get<MFASetupResponse>("/auth/mfa/setup"),
307
+
308
+ enable: (token: string): Promise<MFARecoveryCodesResponse> => {
309
+ const body: MFAEnableBody = { token };
310
+ return this.post<MFARecoveryCodesResponse>("/auth/mfa/enable", body);
311
+ },
312
+
313
+ disable: (): Promise<{ ok: boolean }> => this.post<{ ok: boolean }>("/auth/mfa/disable"),
314
+
315
+ confirm: (token: string, mfaToken: string): Promise<{ ok: boolean }> => {
316
+ const body: MFAConfirmBody = { token, mfaToken };
317
+ return this.post<{ ok: boolean }>("/auth/mfa/confirm", body);
318
+ },
319
+
320
+ recovery: (code: string, mfaToken: string): Promise<{ ok: boolean }> => {
321
+ const body: MFARecoveryBody = { code, mfaToken };
322
+ return this.post<{ ok: boolean }>("/auth/mfa/recovery", body);
323
+ },
324
+
325
+ recoveryCodes: (): Promise<MFARecoveryCodesResponse> =>
326
+ this.get<MFARecoveryCodesResponse>("/auth/mfa/recovery-codes"),
327
+
328
+ regenerateCodes: (): Promise<MFARecoveryCodesResponse> =>
329
+ this.post<MFARecoveryCodesResponse>("/auth/mfa/recovery-codes/regenerate"),
330
+
331
+ sendBackupCode: (email: string): Promise<{ message: string }> =>
332
+ this.post<{ message: string }>("/auth/mfa/send-backup-code", { email }),
333
+
334
+ status: (): Promise<MFAStatusResponse> => this.get<MFAStatusResponse>("/auth/mfa/status"),
335
+ };
336
+
337
+ // =====================================================================
338
+ // Users
339
+ // =====================================================================
340
+ users = {
341
+ me: <T = Record<string, unknown>>(): Promise<T> => this.get<T>("/users/@me"),
342
+
343
+ get: <T = Record<string, unknown>>(userId: string): Promise<T> =>
344
+ this.get<T>(`/users/${userId}`),
345
+
346
+ update: <T = Record<string, unknown>>(data: UpdateUserInput): Promise<T> =>
347
+ this.put<T>("/users", data),
348
+
349
+ delete: (): Promise<void> => this.del("/users"),
350
+ };
351
+
352
+ // =====================================================================
353
+ // API Keys
354
+ // =====================================================================
355
+ apiKeys = {
356
+ create: <T = Record<string, unknown>>(
357
+ name: string,
358
+ permissions: ("read" | "write")[] = ["read", "write"],
359
+ ): Promise<T> => {
360
+ const body: CreateApiKeyInput = { name, permissions };
361
+ return this.post<T>("/api-keys", body);
362
+ },
363
+
364
+ list: <T = Record<string, unknown>>(): Promise<T[]> => this.get<T[]>("/api-keys/@all-user"),
365
+
366
+ get: <T = Record<string, unknown>>(id: string): Promise<T> => this.get<T>(`/api-keys/${id}`),
367
+
368
+ update: <T = Record<string, unknown>>(id: string, data: UpdateApiKeyInput): Promise<T> =>
369
+ this.put<T>(`/api-keys/${id}`, data),
370
+
371
+ delete: (id: string): Promise<void> => this.del(`/api-keys/${id}`),
372
+ };
373
+
374
+ // =====================================================================
375
+ // Projects
376
+ // =====================================================================
377
+ projects = {
378
+ create: <T = Record<string, unknown>>(name: string, description?: string): Promise<T> => {
379
+ const body: CreateProjectInput = {
380
+ name,
381
+ description: description ?? "",
382
+ };
383
+ return this.post<T>("/projects", body);
384
+ },
385
+
386
+ list: <T = Record<string, unknown>>(): Promise<T[]> => this.get<T[]>("/projects/@all"),
387
+
388
+ get: <T = Record<string, unknown>>(id: string): Promise<T> => this.get<T>(`/projects/${id}`),
389
+
390
+ update: <T = Record<string, unknown>>(
391
+ id: string,
392
+ data: { name?: string; description?: string },
393
+ ): Promise<T> => this.put<T>(`/projects/${id}`, data),
394
+
395
+ delete: (id: string): Promise<void> => this.del(`/projects/${id}`),
396
+
397
+ transfer: <T = Record<string, unknown>>(id: string, newOwnerId: string): Promise<T> =>
398
+ this.post<T>(`/projects/${id}/transfer`, { newOwnerId }),
399
+ };
400
+
401
+ // =====================================================================
402
+ // Invites
403
+ // =====================================================================
404
+ invites = {
405
+ create: <T = Record<string, unknown>>(projectId: string, email?: string): Promise<T> => {
406
+ const body: Record<string, string> = {};
407
+ if (email) body.email = email;
408
+ // Send body as-is (empty object when no email) so the server creates an
409
+ // open invite that any authenticated user can accept.
410
+ return this.post<T>(`/projects/${projectId}/invites`, body);
411
+ },
412
+
413
+ /**
414
+ * Accept a project invitation by its unique slug.
415
+ *
416
+ * Uses ``GET`` instead of ``POST`` because the server identifies the
417
+ * invite via the URL slug and does not require a request body.
418
+ */
419
+ accept: <T = Record<string, unknown>>(slug: string): Promise<T> =>
420
+ this.get<T>(`/projects/invites/${slug}`),
421
+ };
422
+
423
+ // =====================================================================
424
+ // Members
425
+ // =====================================================================
426
+ members = {
427
+ remove: (projectId: string, memberId: string): Promise<void> =>
428
+ this.del(`/projects/${projectId}/members/${memberId}`),
429
+
430
+ addPermissions: <T = Record<string, unknown>>(
431
+ projectId: string,
432
+ memberId: string,
433
+ permissions: string[],
434
+ ): Promise<T> =>
435
+ this.post<T>(`/projects/${projectId}/members/${memberId}/permissions`, { permissions }),
436
+
437
+ /**
438
+ * Remove permissions from a member.
439
+ *
440
+ * The ``.then()`` chain discards the API response body because the
441
+ * endpoint returns updated permissions on success but the caller only
442
+ * cares that the operation completed without error.
443
+ */
444
+ removePermissions: <T = Record<string, unknown>>(
445
+ projectId: string,
446
+ memberId: string,
447
+ ): Promise<T> =>
448
+ this.del<T>(`/projects/${projectId}/members/${memberId}/permissions`).then(
449
+ () => undefined as unknown as T,
450
+ ),
451
+ };
452
+
453
+ // =====================================================================
454
+ // Secrets
455
+ // =====================================================================
456
+ secrets = {
457
+ create: <T = Record<string, unknown>>(
458
+ projectId: string,
459
+ name: string,
460
+ value: string,
461
+ notes?: string,
462
+ ): Promise<T> => {
463
+ const body: CreateSecretInput = {
464
+ name,
465
+ value,
466
+ notes: notes ?? "",
467
+ };
468
+ return this.post<T>(`/projects/${projectId}/secrets`, body);
469
+ },
470
+
471
+ get: <T = Record<string, unknown>>(id: string): Promise<T> =>
472
+ this.get<T>(`/projects/secrets/${id}`),
473
+
474
+ list: <T = Record<string, unknown>>(projectId: string): Promise<T[]> =>
475
+ this.get<T[]>(`/projects/${projectId}/secrets`),
476
+
477
+ update: <T = Record<string, unknown>>(id: string, data: UpdateSecretInput): Promise<T> =>
478
+ this.put<T>(`/projects/secrets/${id}`, data),
479
+
480
+ delete: (id: string): Promise<void> => this.del(`/projects/secrets/${id}`),
481
+
482
+ /**
483
+ * Export all secrets of a project as a dotenv-formatted string.
484
+ *
485
+ * Uses raw ``fetch`` instead of the internal ``request`` helper because
486
+ * the endpoint returns ``text/plain``, not ``application/json``, and
487
+ * the response body must not be parsed as JSON.
488
+ *
489
+ * @throws {SecrynApiError} With code ``"EXPORT_ERROR"`` on non-2xx.
490
+ */
491
+ exportDotenv: (projectId: string): Promise<string> =>
492
+ fetch(buildUrl(this.baseUrl, `/projects/${projectId}/secrets/export`), {
493
+ headers: {
494
+ ...(this.cookieJar.getCookieHeader() ? { Cookie: this.cookieJar.getCookieHeader() } : {}),
495
+ ...(this.apiKey ? { "api-key": this.apiKey } : {}),
496
+ },
497
+ }).then((r) => {
498
+ if (!r.ok) throw new SecrynApiError("Export failed", r.status, "EXPORT_ERROR");
499
+ return r.text();
500
+ }),
501
+ };
502
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,45 @@
1
+ /** Debug gate: set ``SECRYN_SDK_DEBUG=1`` to enable info/debug/audit output. */
2
+ const isDebug = process.env["SECRYN_SDK_DEBUG"] === "1";
3
+
4
+ /**
5
+ * Namespaced logger for the Secryn SDK.
6
+ *
7
+ * ``error`` and ``warn`` always write to the console.
8
+ * ``info``, ``debug``, and ``audit`` are gated behind the
9
+ * ``SECRYN_SDK_DEBUG`` environment variable (set to ``"1"`` to enable).
10
+ *
11
+ * All messages are prefixed with ``[secryn]`` for grep-friendly output.
12
+ */
13
+ export const logger = {
14
+ error(message: string, meta?: unknown): void {
15
+ console.error(`[secryn] ERROR ${message}`, meta ?? "");
16
+ },
17
+ warn(message: string, meta?: unknown): void {
18
+ console.warn(`[secryn] WARN ${message}`, meta ?? "");
19
+ },
20
+ info(message: string, meta?: unknown): void {
21
+ if (isDebug) console.info(`[secryn] INFO ${message}`, meta ?? "");
22
+ },
23
+ debug(message: string, meta?: unknown): void {
24
+ if (isDebug) console.debug(`[secryn] DEBUG ${message}`, meta ?? "");
25
+ },
26
+ /**
27
+ * Emit a structured audit log entry.
28
+ *
29
+ * Format: ``[secryn] [AUDIT] <action> actor=<actor> resource=<resource>``.
30
+ * Gated behind ``SECRYN_SDK_DEBUG`` like ``info`` and ``debug``.
31
+ *
32
+ * @param action - CRUD verb or custom action identifier (e.g. ``"login"``).
33
+ * @param actor - Identifier of the principal (user ID, API key prefix).
34
+ * @param resource - Optional resource path or ID affected by the action.
35
+ * @param meta - Optional unstructured context (serialized inline).
36
+ */
37
+ audit(action: string, actor: string, resource?: string, meta?: unknown): void {
38
+ if (isDebug) {
39
+ console.info(
40
+ `[secryn] [AUDIT] ${action} actor=${actor} resource=${resource ?? "-"}`,
41
+ meta ?? "",
42
+ );
43
+ }
44
+ },
45
+ };
package/src/types.ts ADDED
@@ -0,0 +1,102 @@
1
+ /** Request/response DTOs for the Secryn API — mirrored from @repo/shared. */
2
+
3
+ // Auth
4
+
5
+ export interface LoginBody {
6
+ email: string;
7
+ password: string;
8
+ }
9
+
10
+ export interface RegisterBody {
11
+ email: string;
12
+ password: string;
13
+ username?: string;
14
+ }
15
+
16
+ export interface LoginMFAResponse {
17
+ mfaRequired: true;
18
+ mfaToken: string;
19
+ }
20
+
21
+ export interface MFAConfirmBody {
22
+ token: string;
23
+ mfaToken: string;
24
+ }
25
+
26
+ export interface MFARecoveryBody {
27
+ code: string;
28
+ mfaToken: string;
29
+ }
30
+
31
+ export interface MFASetupResponse {
32
+ secret: string;
33
+ qrCode: string;
34
+ otpauthUrl: string;
35
+ }
36
+
37
+ export interface MFAEnableBody {
38
+ token: string;
39
+ }
40
+
41
+ export interface MFAStatusResponse {
42
+ enabled: boolean;
43
+ }
44
+
45
+ export interface MFARecoveryCodesResponse {
46
+ codes: string[];
47
+ }
48
+
49
+ export interface ForgotPasswordBody {
50
+ email: string;
51
+ }
52
+
53
+ export interface ResetPasswordBody {
54
+ token: string;
55
+ password: string;
56
+ }
57
+
58
+ // User
59
+
60
+ export interface UpdateUserInput {
61
+ name?: string;
62
+ email?: string;
63
+ currentPassword?: string;
64
+ newPassword?: string;
65
+ }
66
+
67
+ // Project
68
+
69
+ export interface CreateProjectInput {
70
+ name: string;
71
+ description?: string;
72
+ }
73
+
74
+ // Secret
75
+
76
+ export interface CreateSecretInput {
77
+ name: string;
78
+ value: string;
79
+ notes: string;
80
+ }
81
+
82
+ export interface UpdateSecretInput {
83
+ name?: string;
84
+ value?: string;
85
+ notes?: string;
86
+ }
87
+
88
+ // API Key
89
+
90
+ export type ApiKeyPermission = "read" | "write";
91
+
92
+ export interface CreateApiKeyInput {
93
+ name: string;
94
+ permissions: ApiKeyPermission[];
95
+ }
96
+
97
+ export interface UpdateApiKeyInput {
98
+ name?: string;
99
+ isActive?: boolean;
100
+ addPermissions?: ApiKeyPermission[];
101
+ removePermissions?: ApiKeyPermission[];
102
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ESNext"],
7
+ "verbatimModuleSyntax": true,
8
+ "noEmit": false,
9
+ "declaration": true,
10
+ "types": ["node"],
11
+ "outDir": "./dist",
12
+ "rootDir": "."
13
+ },
14
+ "include": ["src/**/*", "index.ts"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }