@nexys/user-management-client 0.1.0 → 0.2.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.
Files changed (64) hide show
  1. package/README.md +19 -0
  2. package/dist/endpoints/admin-users.d.ts +53 -23
  3. package/dist/endpoints/admin-users.d.ts.map +1 -1
  4. package/dist/endpoints/admin-users.js +56 -23
  5. package/dist/endpoints/admin-users.js.map +1 -1
  6. package/dist/endpoints/api-tokens.d.ts +23 -0
  7. package/dist/endpoints/api-tokens.d.ts.map +1 -0
  8. package/dist/endpoints/api-tokens.js +28 -0
  9. package/dist/endpoints/api-tokens.js.map +1 -0
  10. package/dist/endpoints/auth.d.ts +50 -7
  11. package/dist/endpoints/auth.d.ts.map +1 -1
  12. package/dist/endpoints/auth.js +33 -4
  13. package/dist/endpoints/auth.js.map +1 -1
  14. package/dist/endpoints/index.d.ts +4 -0
  15. package/dist/endpoints/index.d.ts.map +1 -1
  16. package/dist/endpoints/index.js +4 -0
  17. package/dist/endpoints/index.js.map +1 -1
  18. package/dist/endpoints/me.d.ts +14 -1
  19. package/dist/endpoints/me.d.ts.map +1 -1
  20. package/dist/endpoints/me.js +11 -0
  21. package/dist/endpoints/me.js.map +1 -1
  22. package/dist/endpoints/passkeys.d.ts +39 -0
  23. package/dist/endpoints/passkeys.d.ts.map +1 -0
  24. package/dist/endpoints/passkeys.js +53 -0
  25. package/dist/endpoints/passkeys.js.map +1 -0
  26. package/dist/endpoints/projects.d.ts +69 -0
  27. package/dist/endpoints/projects.d.ts.map +1 -0
  28. package/dist/endpoints/projects.js +73 -0
  29. package/dist/endpoints/projects.js.map +1 -0
  30. package/dist/endpoints/tenants.d.ts +72 -103
  31. package/dist/endpoints/tenants.d.ts.map +1 -1
  32. package/dist/endpoints/tenants.js +64 -82
  33. package/dist/endpoints/tenants.js.map +1 -1
  34. package/dist/endpoints/two-factor.d.ts +50 -0
  35. package/dist/endpoints/two-factor.d.ts.map +1 -0
  36. package/dist/endpoints/two-factor.js +64 -0
  37. package/dist/endpoints/two-factor.js.map +1 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/types.d.ts +128 -44
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +1 -1
  45. package/dist/types.js.map +1 -1
  46. package/package.json +7 -2
  47. package/src/endpoint.test.ts +158 -0
  48. package/src/endpoint.ts +123 -0
  49. package/src/endpoints/admin-audit.ts +60 -0
  50. package/src/endpoints/admin-keys.ts +37 -0
  51. package/src/endpoints/admin-users.ts +172 -0
  52. package/src/endpoints/api-tokens.ts +37 -0
  53. package/src/endpoints/auth.ts +203 -0
  54. package/src/endpoints/health.ts +50 -0
  55. package/src/endpoints/index.ts +12 -0
  56. package/src/endpoints/me.ts +79 -0
  57. package/src/endpoints/passkeys.ts +77 -0
  58. package/src/endpoints/projects.ts +117 -0
  59. package/src/endpoints/refresh-tokens.ts +25 -0
  60. package/src/endpoints/tenants.ts +313 -0
  61. package/src/endpoints/two-factor.ts +84 -0
  62. package/src/index.ts +26 -0
  63. package/src/result.ts +39 -0
  64. package/src/types.ts +285 -0
@@ -0,0 +1,313 @@
1
+ import type { Endpoint } from "../endpoint.js";
2
+ import type { ApiError } from "../result.js";
3
+ import { parseApiError } from "../result.js";
4
+ import type {
5
+ FullOrganization,
6
+ Invitation,
7
+ Member,
8
+ MyTenantPermissions,
9
+ RoleRow,
10
+ TenantRole,
11
+ TenantRow,
12
+ } from "../types.js";
13
+
14
+ export const adminListTenants: Endpoint<void, { tenants: TenantRow[] }, ApiError> = {
15
+ method: "GET",
16
+ path: () => "/api/admin/tenants",
17
+ parseOutput: (data) => data as { tenants: TenantRow[] },
18
+ parseError: parseApiError,
19
+ };
20
+
21
+ export const createTenant: Endpoint<
22
+ {
23
+ name: string;
24
+ slug?: string;
25
+ /**
26
+ * Attach the tenant to a project, gated by the project's settings
27
+ * (`allowTenantCreation`, `tenancyMode`).
28
+ */
29
+ projectId?: string;
30
+ },
31
+ TenantRow,
32
+ ApiError
33
+ > = {
34
+ method: "POST",
35
+ path: () => "/api/auth/organization/create",
36
+ body: (input) => input,
37
+ parseOutput: (data) => data as TenantRow,
38
+ parseError: parseApiError,
39
+ };
40
+
41
+ /**
42
+ * Update tenant name/slug/logo and the per-tenant `metadata` settings
43
+ * blob. Omitted fields keep their current value; `metadata: null`
44
+ * clears the blob. Requires org owner/admin (or a superadmin).
45
+ */
46
+ export const updateTenant: Endpoint<
47
+ {
48
+ organizationId: string;
49
+ data: {
50
+ name?: string;
51
+ slug?: string;
52
+ logo?: string | null;
53
+ metadata?: Record<string, unknown> | null;
54
+ };
55
+ },
56
+ {
57
+ id: string;
58
+ name: string;
59
+ slug: string;
60
+ logo: string | null;
61
+ metadata: Record<string, unknown> | null;
62
+ },
63
+ ApiError
64
+ > = {
65
+ method: "POST",
66
+ path: () => "/api/auth/organization/update",
67
+ body: (input) => input,
68
+ parseOutput: (data) =>
69
+ data as {
70
+ id: string;
71
+ name: string;
72
+ slug: string;
73
+ logo: string | null;
74
+ metadata: Record<string, unknown> | null;
75
+ },
76
+ parseError: parseApiError,
77
+ };
78
+
79
+ /**
80
+ * Owner self-service tenant delete. Cascades members, invitations, and
81
+ * roles. Requires the caller to be the org owner (or a superadmin). For
82
+ * the superadmin system-wide path see {@link adminDeleteTenant}.
83
+ */
84
+ export const deleteTenant: Endpoint<
85
+ { organizationId: string },
86
+ { ok: true; id: string },
87
+ ApiError
88
+ > = {
89
+ method: "POST",
90
+ path: () => "/api/auth/organization/delete",
91
+ body: (input) => input,
92
+ parseOutput: (data) => data as { ok: true; id: string },
93
+ parseError: parseApiError,
94
+ };
95
+
96
+ export const setActiveTenant: Endpoint<
97
+ { organizationId: string | null },
98
+ { session: { activeOrganizationId: string | null } },
99
+ ApiError
100
+ > = {
101
+ method: "POST",
102
+ path: () => "/api/auth/organization/set-active",
103
+ body: (input) => input,
104
+ parseOutput: (data) =>
105
+ data as { session: { activeOrganizationId: string | null } },
106
+ parseError: parseApiError,
107
+ };
108
+
109
+ export const getFullTenant: Endpoint<
110
+ { organizationId?: string },
111
+ FullOrganization,
112
+ ApiError
113
+ > = {
114
+ method: "GET",
115
+ path: () => "/api/auth/organization/get-full-organization",
116
+ query: (input) => ({ organizationId: input.organizationId }),
117
+ parseOutput: (data) => data as FullOrganization,
118
+ parseError: parseApiError,
119
+ };
120
+
121
+ export const inviteMember: Endpoint<
122
+ { email: string; role: TenantRole; organizationId: string },
123
+ Invitation,
124
+ ApiError
125
+ > = {
126
+ method: "POST",
127
+ path: () => "/api/auth/organization/invite-member",
128
+ body: (input) => input,
129
+ parseOutput: (data) => data as Invitation,
130
+ parseError: parseApiError,
131
+ };
132
+
133
+ export const acceptInvitation: Endpoint<
134
+ { invitationId: string },
135
+ { memberId: string; organizationId: string; role: TenantRole },
136
+ ApiError
137
+ > = {
138
+ method: "POST",
139
+ path: () => "/api/auth/organization/accept-invitation",
140
+ body: (input) => input,
141
+ parseOutput: (data) =>
142
+ data as { memberId: string; organizationId: string; role: TenantRole },
143
+ parseError: parseApiError,
144
+ };
145
+
146
+ export const updateMemberRole: Endpoint<
147
+ { memberId: string; role: TenantRole; organizationId: string },
148
+ { ok: true },
149
+ ApiError
150
+ > = {
151
+ method: "POST",
152
+ path: () => "/api/auth/organization/update-member-role",
153
+ body: (input) => input,
154
+ parseOutput: () => ({ ok: true }),
155
+ parseError: parseApiError,
156
+ };
157
+
158
+ /**
159
+ * Direct member assignment — no invitation round-trip. The target
160
+ * user must already exist (lookup by userId or email); requires
161
+ * tenant owner/admin or a superadmin.
162
+ */
163
+ export const addMember: Endpoint<
164
+ {
165
+ organizationId: string;
166
+ role: TenantRole;
167
+ userId?: string;
168
+ email?: string;
169
+ },
170
+ Member,
171
+ ApiError
172
+ > = {
173
+ method: "POST",
174
+ path: () => "/api/auth/organization/add-member",
175
+ body: (input) => input,
176
+ parseOutput: (data) => data as Member,
177
+ parseError: parseApiError,
178
+ };
179
+
180
+ export const removeMember: Endpoint<
181
+ { memberId: string; organizationId: string },
182
+ { ok: true },
183
+ ApiError
184
+ > = {
185
+ method: "POST",
186
+ path: () => "/api/auth/organization/remove-member",
187
+ body: (input) => input,
188
+ parseOutput: () => ({ ok: true }),
189
+ parseError: parseApiError,
190
+ };
191
+
192
+ export const adminDeleteTenant: Endpoint<
193
+ { id: string },
194
+ { ok: true },
195
+ ApiError
196
+ > = {
197
+ method: "DELETE",
198
+ path: (input) => `/api/admin/tenants/${input.id}`,
199
+ parseOutput: () => ({ ok: true }),
200
+ parseError: parseApiError,
201
+ };
202
+
203
+ export const listTenantRoles: Endpoint<
204
+ { orgId: string },
205
+ { roles: RoleRow[] },
206
+ ApiError
207
+ > = {
208
+ method: "GET",
209
+ path: (input) => `/api/tenants/${input.orgId}/roles`,
210
+ parseOutput: (data) => data as { roles: RoleRow[] },
211
+ parseError: parseApiError,
212
+ };
213
+
214
+ export const createTenantRole: Endpoint<
215
+ { orgId: string; name: string; description?: string; permissions?: string[] },
216
+ RoleRow,
217
+ ApiError
218
+ > = {
219
+ method: "POST",
220
+ path: (input) => `/api/tenants/${input.orgId}/roles`,
221
+ body: (input) => ({
222
+ name: input.name,
223
+ description: input.description,
224
+ permissions: input.permissions,
225
+ }),
226
+ parseOutput: (data) => data as RoleRow,
227
+ parseError: parseApiError,
228
+ };
229
+
230
+ /**
231
+ * Update a role's name/description and, when `permissions` is present,
232
+ * replace its permission set wholesale.
233
+ */
234
+ export const updateTenantRole: Endpoint<
235
+ {
236
+ orgId: string;
237
+ roleId: string;
238
+ name?: string;
239
+ description?: string;
240
+ permissions?: string[];
241
+ },
242
+ RoleRow,
243
+ ApiError
244
+ > = {
245
+ method: "PATCH",
246
+ path: (input) => `/api/tenants/${input.orgId}/roles/${input.roleId}`,
247
+ body: (input) => ({
248
+ name: input.name,
249
+ description: input.description,
250
+ permissions: input.permissions,
251
+ }),
252
+ parseOutput: (data) => data as RoleRow,
253
+ parseError: parseApiError,
254
+ };
255
+
256
+ export const deleteTenantRole: Endpoint<
257
+ { orgId: string; roleId: string },
258
+ { ok: true },
259
+ ApiError
260
+ > = {
261
+ method: "DELETE",
262
+ path: (input) => `/api/tenants/${input.orgId}/roles/${input.roleId}`,
263
+ parseOutput: () => ({ ok: true }),
264
+ parseError: parseApiError,
265
+ };
266
+
267
+ /** Custom roles assigned to one member (with their permissions). */
268
+ export const listMemberRoles: Endpoint<
269
+ { orgId: string; memberId: string },
270
+ { roles: RoleRow[] },
271
+ ApiError
272
+ > = {
273
+ method: "GET",
274
+ path: (input) => `/api/tenants/${input.orgId}/members/${input.memberId}/roles`,
275
+ parseOutput: (data) => data as { roles: RoleRow[] },
276
+ parseError: parseApiError,
277
+ };
278
+
279
+ export const assignMemberRole: Endpoint<
280
+ { orgId: string; memberId: string; roleId: string },
281
+ { ok: true; roleId: string; memberId: string },
282
+ ApiError
283
+ > = {
284
+ method: "POST",
285
+ path: (input) => `/api/tenants/${input.orgId}/members/${input.memberId}/roles`,
286
+ body: (input) => ({ roleId: input.roleId }),
287
+ parseOutput: (data) => data as { ok: true; roleId: string; memberId: string },
288
+ parseError: parseApiError,
289
+ };
290
+
291
+ export const removeMemberRole: Endpoint<
292
+ { orgId: string; memberId: string; roleId: string },
293
+ { ok: true },
294
+ ApiError
295
+ > = {
296
+ method: "DELETE",
297
+ path: (input) =>
298
+ `/api/tenants/${input.orgId}/members/${input.memberId}/roles/${input.roleId}`,
299
+ parseOutput: () => ({ ok: true }),
300
+ parseError: parseApiError,
301
+ };
302
+
303
+ /** The caller's own effective role/permissions inside a tenant. */
304
+ export const myTenantPermissions: Endpoint<
305
+ { orgId: string },
306
+ MyTenantPermissions,
307
+ ApiError
308
+ > = {
309
+ method: "GET",
310
+ path: (input) => `/api/tenants/${input.orgId}/permissions/me`,
311
+ parseOutput: (data) => data as MyTenantPermissions,
312
+ parseError: parseApiError,
313
+ };
@@ -0,0 +1,84 @@
1
+ import type { Endpoint } from "../endpoint.js";
2
+ import type { ApiError } from "../result.js";
3
+ import { parseApiError } from "../result.js";
4
+ import type { AuthSuccess, TwoFactorEnableResponse } from "../types.js";
5
+
6
+ /**
7
+ * Start TOTP enrolment (cookie-auth). Returns the secret, the
8
+ * `otpauth://` URI to render as a QR, and 10 single-use backup codes
9
+ * that are shown exactly once. Enrolment isn't active until
10
+ * {@link twoFactorConfirm} verifies the first code.
11
+ */
12
+ export const twoFactorEnable: Endpoint<void, TwoFactorEnableResponse, ApiError> = {
13
+ method: "POST",
14
+ path: () => "/api/auth/two-factor/enable",
15
+ parseOutput: (data) => data as TwoFactorEnableResponse,
16
+ parseError: parseApiError,
17
+ };
18
+
19
+ export const twoFactorConfirm: Endpoint<{ code: string }, { ok: true }, ApiError> = {
20
+ method: "POST",
21
+ path: () => "/api/auth/two-factor/confirm",
22
+ body: (input) => input,
23
+ parseOutput: () => ({ ok: true }),
24
+ parseError: parseApiError,
25
+ };
26
+
27
+ /** Requires a current TOTP code; wipes the secret + backup codes. */
28
+ export const twoFactorDisable: Endpoint<{ code: string }, { ok: true }, ApiError> = {
29
+ method: "POST",
30
+ path: () => "/api/auth/two-factor/disable",
31
+ body: (input) => input,
32
+ parseOutput: () => ({ ok: true }),
33
+ parseError: parseApiError,
34
+ };
35
+
36
+ /**
37
+ * Complete a password sign-in that returned a
38
+ * {@link TwoFactorChallenge}: `token` is the challenge token, `code`
39
+ * the current TOTP code. Sets the session cookie on success.
40
+ */
41
+ export const verifyTotp: Endpoint<
42
+ { token: string; code: string },
43
+ AuthSuccess,
44
+ ApiError
45
+ > = {
46
+ method: "POST",
47
+ path: () => "/api/auth/two-factor/verify-totp",
48
+ body: (input) => input,
49
+ parseOutput: (data) => data as AuthSuccess,
50
+ parseError: parseApiError,
51
+ };
52
+
53
+ /**
54
+ * Step-up re-verification for an already authenticated caller (cookie
55
+ * or bearer). Answers whether `code` is a current TOTP code for the
56
+ * caller without touching sessions or tokens — resource servers use it
57
+ * to gate sensitive actions (payments, key exports) on a fresh second
58
+ * factor. `enabled: false` means the caller has no verified TOTP
59
+ * enrolment (and `valid` is then always false).
60
+ */
61
+ export const twoFactorStepUp: Endpoint<
62
+ { code: string },
63
+ { enabled: boolean; valid: boolean },
64
+ ApiError
65
+ > = {
66
+ method: "POST",
67
+ path: () => "/api/me/two-factor/step-up",
68
+ body: (input) => input,
69
+ parseOutput: (data) => data as { enabled: boolean; valid: boolean },
70
+ parseError: parseApiError,
71
+ };
72
+
73
+ /** Like {@link verifyTotp} but consumes a single-use backup code. */
74
+ export const verifyBackupCode: Endpoint<
75
+ { token: string; code: string },
76
+ AuthSuccess,
77
+ ApiError
78
+ > = {
79
+ method: "POST",
80
+ path: () => "/api/auth/two-factor/verify-backup-code",
81
+ body: (input) => input,
82
+ parseOutput: (data) => data as AuthSuccess,
83
+ parseError: parseApiError,
84
+ };
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ // Core types & runtime
2
+ export {
3
+ createClient,
4
+ type ClientConfig,
5
+ type Endpoint,
6
+ type EndpointError,
7
+ type EndpointInput,
8
+ type EndpointOutput,
9
+ type Method,
10
+ type UmClient,
11
+ } from "./endpoint.js";
12
+ export {
13
+ err,
14
+ ok,
15
+ parseApiError,
16
+ type ApiError,
17
+ type NetworkError,
18
+ type Result,
19
+ } from "./result.js";
20
+
21
+ // Endpoints — all named exports, grouped by domain
22
+ export * from "./endpoints/index.js";
23
+
24
+ // Wire types
25
+ export type * from "./types.js";
26
+ export { isTwoFactorChallenge } from "./types.js";
package/src/result.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Result + error types. Endpoints never throw — `call` collapses
3
+ * thrown exceptions into `{ kind: "network" }` and HTTP errors into
4
+ * whatever shape `endpoint.parseError` produces.
5
+ */
6
+
7
+ export type Result<T, E> = { ok: true; data: T } | { ok: false; error: E };
8
+
9
+ export type NetworkError = { kind: "network"; message: string };
10
+
11
+ /**
12
+ * Default HTTP error envelope mirroring the rust server's
13
+ * `{ code, message }` shape. Individual endpoints can narrow this
14
+ * with a discriminated union when they want callers to switch on
15
+ * specific codes (e.g. sign-in's INVALID_EMAIL_OR_PASSWORD).
16
+ */
17
+ export type ApiError = {
18
+ kind: "http";
19
+ status: number;
20
+ code: string | undefined;
21
+ message: string;
22
+ };
23
+
24
+ /**
25
+ * Lift a JSON error body into `ApiError`. Tolerates non-JSON
26
+ * (data === null) and missing fields.
27
+ */
28
+ export const parseApiError = (status: number, data: unknown): ApiError => {
29
+ const body = (data ?? {}) as { code?: string; error?: string; message?: string };
30
+ return {
31
+ kind: "http",
32
+ status,
33
+ code: body.code ?? body.error,
34
+ message: body.message ?? body.error ?? `Request failed (${status})`,
35
+ };
36
+ };
37
+
38
+ export const ok = <T>(data: T): Result<T, never> => ({ ok: true, data });
39
+ export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });