@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.
- package/README.md +19 -0
- package/dist/endpoints/admin-users.d.ts +53 -23
- package/dist/endpoints/admin-users.d.ts.map +1 -1
- package/dist/endpoints/admin-users.js +56 -23
- package/dist/endpoints/admin-users.js.map +1 -1
- package/dist/endpoints/api-tokens.d.ts +23 -0
- package/dist/endpoints/api-tokens.d.ts.map +1 -0
- package/dist/endpoints/api-tokens.js +28 -0
- package/dist/endpoints/api-tokens.js.map +1 -0
- package/dist/endpoints/auth.d.ts +50 -7
- package/dist/endpoints/auth.d.ts.map +1 -1
- package/dist/endpoints/auth.js +33 -4
- package/dist/endpoints/auth.js.map +1 -1
- package/dist/endpoints/index.d.ts +4 -0
- package/dist/endpoints/index.d.ts.map +1 -1
- package/dist/endpoints/index.js +4 -0
- package/dist/endpoints/index.js.map +1 -1
- package/dist/endpoints/me.d.ts +14 -1
- package/dist/endpoints/me.d.ts.map +1 -1
- package/dist/endpoints/me.js +11 -0
- package/dist/endpoints/me.js.map +1 -1
- package/dist/endpoints/passkeys.d.ts +39 -0
- package/dist/endpoints/passkeys.d.ts.map +1 -0
- package/dist/endpoints/passkeys.js +53 -0
- package/dist/endpoints/passkeys.js.map +1 -0
- package/dist/endpoints/projects.d.ts +69 -0
- package/dist/endpoints/projects.d.ts.map +1 -0
- package/dist/endpoints/projects.js +73 -0
- package/dist/endpoints/projects.js.map +1 -0
- package/dist/endpoints/tenants.d.ts +72 -103
- package/dist/endpoints/tenants.d.ts.map +1 -1
- package/dist/endpoints/tenants.js +64 -82
- package/dist/endpoints/tenants.js.map +1 -1
- package/dist/endpoints/two-factor.d.ts +50 -0
- package/dist/endpoints/two-factor.d.ts.map +1 -0
- package/dist/endpoints/two-factor.js +64 -0
- package/dist/endpoints/two-factor.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +128 -44
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -2
- package/src/endpoint.test.ts +158 -0
- package/src/endpoint.ts +123 -0
- package/src/endpoints/admin-audit.ts +60 -0
- package/src/endpoints/admin-keys.ts +37 -0
- package/src/endpoints/admin-users.ts +172 -0
- package/src/endpoints/api-tokens.ts +37 -0
- package/src/endpoints/auth.ts +203 -0
- package/src/endpoints/health.ts +50 -0
- package/src/endpoints/index.ts +12 -0
- package/src/endpoints/me.ts +79 -0
- package/src/endpoints/passkeys.ts +77 -0
- package/src/endpoints/projects.ts +117 -0
- package/src/endpoints/refresh-tokens.ts +25 -0
- package/src/endpoints/tenants.ts +313 -0
- package/src/endpoints/two-factor.ts +84 -0
- package/src/index.ts +26 -0
- package/src/result.ts +39 -0
- package/src/types.ts +285 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
import type { AuthSuccess, SignInResult } from "../types.js";
|
|
5
|
+
|
|
6
|
+
/** Sign-up surfaces a couple of specific codes the UI may want to switch on. */
|
|
7
|
+
export type SignUpError =
|
|
8
|
+
| ApiError
|
|
9
|
+
| { kind: "http"; status: 422; code: "USER_ALREADY_EXISTS"; message: string }
|
|
10
|
+
| { kind: "http"; status: 422; code: "PASSWORD_TOO_SHORT"; message: string };
|
|
11
|
+
|
|
12
|
+
export const signUp: Endpoint<
|
|
13
|
+
{
|
|
14
|
+
name: string;
|
|
15
|
+
email: string;
|
|
16
|
+
password: string;
|
|
17
|
+
/**
|
|
18
|
+
* Project-scoped sign-up: the target project must exist and have
|
|
19
|
+
* `allowSignup` enabled (403 `signup_disabled` otherwise).
|
|
20
|
+
*/
|
|
21
|
+
projectId?: string;
|
|
22
|
+
},
|
|
23
|
+
AuthSuccess,
|
|
24
|
+
SignUpError
|
|
25
|
+
> = {
|
|
26
|
+
method: "POST",
|
|
27
|
+
path: () => "/api/auth/sign-up/email",
|
|
28
|
+
body: (input) => input,
|
|
29
|
+
parseOutput: (data) => data as AuthSuccess,
|
|
30
|
+
parseError: parseApiError,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Sign-in collapses bad-email / bad-password into one enumeration-safe code. */
|
|
34
|
+
export type SignInError =
|
|
35
|
+
| ApiError
|
|
36
|
+
| { kind: "http"; status: 401; code: "INVALID_EMAIL_OR_PASSWORD"; message: string }
|
|
37
|
+
| { kind: "http"; status: 403; code: "INACTIVE_USER"; message: string };
|
|
38
|
+
|
|
39
|
+
export const signIn: Endpoint<
|
|
40
|
+
{ email: string; password: string },
|
|
41
|
+
SignInResult,
|
|
42
|
+
SignInError
|
|
43
|
+
> = {
|
|
44
|
+
method: "POST",
|
|
45
|
+
path: () => "/api/auth/sign-in/email",
|
|
46
|
+
body: (input) => input,
|
|
47
|
+
parseOutput: (data) => data as SignInResult,
|
|
48
|
+
parseError: parseApiError,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Full user + session snapshot; `null` body when unauthenticated. */
|
|
52
|
+
export const getSession: Endpoint<void, AuthSuccess | null, ApiError> = {
|
|
53
|
+
method: "GET",
|
|
54
|
+
path: () => "/api/auth/get-session",
|
|
55
|
+
parseOutput: (data) => data as AuthSuccess | null,
|
|
56
|
+
parseError: parseApiError,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const signOut: Endpoint<void, { success: boolean }, ApiError> = {
|
|
60
|
+
method: "POST",
|
|
61
|
+
path: () => "/api/auth/sign-out",
|
|
62
|
+
parseOutput: (data) => data as { success: boolean },
|
|
63
|
+
parseError: parseApiError,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const requestPasswordReset: Endpoint<{ email: string }, { ok: true }, ApiError> = {
|
|
67
|
+
method: "POST",
|
|
68
|
+
path: () => "/api/auth/forget-password",
|
|
69
|
+
body: (input) => input,
|
|
70
|
+
parseOutput: () => ({ ok: true }),
|
|
71
|
+
parseError: parseApiError,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const resetPassword: Endpoint<
|
|
75
|
+
{ token: string; newPassword: string },
|
|
76
|
+
{ ok: true },
|
|
77
|
+
ApiError
|
|
78
|
+
> = {
|
|
79
|
+
method: "POST",
|
|
80
|
+
path: () => "/api/auth/reset-password",
|
|
81
|
+
body: (input) => ({ token: input.token, newPassword: input.newPassword }),
|
|
82
|
+
parseOutput: () => ({ ok: true }),
|
|
83
|
+
parseError: parseApiError,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const requestMagicLink: Endpoint<{ email: string }, { ok: true }, ApiError> = {
|
|
87
|
+
method: "POST",
|
|
88
|
+
path: () => "/api/auth/sign-in/magic-link",
|
|
89
|
+
body: (input) => input,
|
|
90
|
+
parseOutput: () => ({ ok: true }),
|
|
91
|
+
parseError: parseApiError,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const verifyMagicLink: Endpoint<{ token: string }, { ok: true }, ApiError> = {
|
|
95
|
+
method: "GET",
|
|
96
|
+
path: () => "/api/auth/magic-link/verify",
|
|
97
|
+
query: ({ token }) => ({ token }),
|
|
98
|
+
parseOutput: () => ({ ok: true }),
|
|
99
|
+
parseError: parseApiError,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Better Auth admin-plugin user create. Superadmin/useradmin only;
|
|
104
|
+
* the account lands active + email-verified with a password
|
|
105
|
+
* credential. Returns the full user under `user`.
|
|
106
|
+
*/
|
|
107
|
+
export const adminCreateUser: Endpoint<
|
|
108
|
+
{
|
|
109
|
+
email: string;
|
|
110
|
+
password: string;
|
|
111
|
+
name?: string;
|
|
112
|
+
firstName?: string;
|
|
113
|
+
lastName?: string;
|
|
114
|
+
role?: string;
|
|
115
|
+
},
|
|
116
|
+
{ user: { id: string; email: string } },
|
|
117
|
+
ApiError
|
|
118
|
+
> = {
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: () => "/api/auth/admin/create-user",
|
|
121
|
+
body: (input) => input,
|
|
122
|
+
parseOutput: (data) => data as { user: { id: string; email: string } },
|
|
123
|
+
parseError: parseApiError,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Better Auth admin-plugin user update — edit name and/or email.
|
|
128
|
+
* Email keeps its verified flag; uniqueness enforced (422).
|
|
129
|
+
*/
|
|
130
|
+
export const adminUpdateUser: Endpoint<
|
|
131
|
+
{
|
|
132
|
+
userId: string;
|
|
133
|
+
name?: string;
|
|
134
|
+
firstName?: string;
|
|
135
|
+
lastName?: string;
|
|
136
|
+
email?: string;
|
|
137
|
+
},
|
|
138
|
+
{ user: { id: string; email: string; name: string } },
|
|
139
|
+
ApiError
|
|
140
|
+
> = {
|
|
141
|
+
method: "POST",
|
|
142
|
+
path: () => "/api/auth/admin/update-user",
|
|
143
|
+
body: (input) => input,
|
|
144
|
+
parseOutput: (data) => data as { user: { id: string; email: string; name: string } },
|
|
145
|
+
parseError: parseApiError,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Better Auth admin-plugin user delete (soft delete — sets
|
|
150
|
+
* `deletedAt` and revokes the user's sessions). Idempotent.
|
|
151
|
+
*/
|
|
152
|
+
export const adminRemoveUser: Endpoint<
|
|
153
|
+
{ userId: string },
|
|
154
|
+
{ success: true; sessionsRevoked: number; refreshTokensRevoked: number },
|
|
155
|
+
ApiError
|
|
156
|
+
> = {
|
|
157
|
+
method: "POST",
|
|
158
|
+
path: () => "/api/auth/admin/remove-user",
|
|
159
|
+
body: (input) => input,
|
|
160
|
+
parseOutput: (data) =>
|
|
161
|
+
data as { success: true; sessionsRevoked: number; refreshTokensRevoked: number },
|
|
162
|
+
parseError: parseApiError,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const getProviders: Endpoint<void, { providers: string[] }, ApiError> = {
|
|
166
|
+
method: "GET",
|
|
167
|
+
path: () => "/api/providers",
|
|
168
|
+
parseOutput: (data) => data as { providers: string[] },
|
|
169
|
+
parseError: parseApiError,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const socialSignIn: Endpoint<
|
|
173
|
+
{
|
|
174
|
+
provider: string;
|
|
175
|
+
callbackURL?: string;
|
|
176
|
+
/**
|
|
177
|
+
* Project-scoped flow. If the callback would create a *new* user,
|
|
178
|
+
* the project's `allowSignup` setting gates it; existing users
|
|
179
|
+
* sign in regardless.
|
|
180
|
+
*/
|
|
181
|
+
projectId?: string;
|
|
182
|
+
},
|
|
183
|
+
{ url: string; redirect: true },
|
|
184
|
+
ApiError
|
|
185
|
+
> = {
|
|
186
|
+
method: "POST",
|
|
187
|
+
path: () => "/api/auth/sign-in/social",
|
|
188
|
+
body: (input) => input,
|
|
189
|
+
parseOutput: (data) => data as { url: string; redirect: true },
|
|
190
|
+
parseError: parseApiError,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const changePassword: Endpoint<
|
|
194
|
+
{ currentPassword: string; newPassword: string },
|
|
195
|
+
{ ok: true },
|
|
196
|
+
ApiError
|
|
197
|
+
> = {
|
|
198
|
+
method: "POST",
|
|
199
|
+
path: () => "/api/auth/change-password",
|
|
200
|
+
body: (input) => input,
|
|
201
|
+
parseOutput: () => ({ ok: true }),
|
|
202
|
+
parseError: parseApiError,
|
|
203
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
|
|
5
|
+
export const health: Endpoint<void, { ok: boolean }, ApiError> = {
|
|
6
|
+
method: "GET",
|
|
7
|
+
path: () => "/health",
|
|
8
|
+
parseOutput: (data) => data as { ok: boolean },
|
|
9
|
+
parseError: parseApiError,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface AuthHealth {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
/** Server crate version (Cargo `version`) — baked in at compile time. */
|
|
15
|
+
version: string;
|
|
16
|
+
/** UTC ISO-8601 build timestamp seeded by CI via `BUILD_DATE`;
|
|
17
|
+
* `"unknown"` outside CI. */
|
|
18
|
+
date: string;
|
|
19
|
+
/** Build commit SHA. `null` in dev builds where `GIT_SHA` was unset. */
|
|
20
|
+
sha: string | null;
|
|
21
|
+
/** Release tag, set only on tagged builds. */
|
|
22
|
+
tag: string | null;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
uptimeSeconds: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const authHealth: Endpoint<void, AuthHealth, ApiError> = {
|
|
28
|
+
method: "GET",
|
|
29
|
+
path: () => "/api/auth/health",
|
|
30
|
+
parseOutput: (data) => data as AuthHealth,
|
|
31
|
+
parseError: parseApiError,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export interface JwksKey {
|
|
35
|
+
kid: string;
|
|
36
|
+
kty: string;
|
|
37
|
+
use?: string;
|
|
38
|
+
alg?: string;
|
|
39
|
+
crv?: string;
|
|
40
|
+
x?: string;
|
|
41
|
+
// additional fields per JWK spec; left open
|
|
42
|
+
[k: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const jwks: Endpoint<void, { keys: JwksKey[] }, ApiError> = {
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: () => "/api/auth/jwks",
|
|
48
|
+
parseOutput: (data) => data as { keys: JwksKey[] },
|
|
49
|
+
parseError: parseApiError,
|
|
50
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./api-tokens.js";
|
|
2
|
+
export * from "./auth.js";
|
|
3
|
+
export * from "./me.js";
|
|
4
|
+
export * from "./passkeys.js";
|
|
5
|
+
export * from "./two-factor.js";
|
|
6
|
+
export * from "./refresh-tokens.js";
|
|
7
|
+
export * from "./admin-users.js";
|
|
8
|
+
export * from "./admin-audit.js";
|
|
9
|
+
export * from "./admin-keys.js";
|
|
10
|
+
export * from "./health.js";
|
|
11
|
+
export * from "./tenants.js";
|
|
12
|
+
export * from "./projects.js";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
import type {
|
|
5
|
+
MeResponse,
|
|
6
|
+
TokenMintResponse,
|
|
7
|
+
TokenRefreshResponse,
|
|
8
|
+
UserMembership,
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
|
|
11
|
+
export const me: Endpoint<void, MeResponse, ApiError> = {
|
|
12
|
+
method: "GET",
|
|
13
|
+
path: () => "/api/me",
|
|
14
|
+
parseOutput: (data) => data as MeResponse,
|
|
15
|
+
parseError: parseApiError,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The caller's own tenant memberships — self-service counterpart of
|
|
20
|
+
* the admin-only `adminListUserTenants`, for org discovery before
|
|
21
|
+
* `setActiveTenant`.
|
|
22
|
+
*/
|
|
23
|
+
export const myTenants: Endpoint<
|
|
24
|
+
void,
|
|
25
|
+
{ memberships: UserMembership[] },
|
|
26
|
+
ApiError
|
|
27
|
+
> = {
|
|
28
|
+
method: "GET",
|
|
29
|
+
path: () => "/api/me/tenants",
|
|
30
|
+
parseOutput: (data) => data as { memberships: UserMembership[] },
|
|
31
|
+
parseError: parseApiError,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const updateProfile: Endpoint<
|
|
35
|
+
{ name?: string; image?: string | null },
|
|
36
|
+
{ ok: true },
|
|
37
|
+
ApiError
|
|
38
|
+
> = {
|
|
39
|
+
method: "PATCH",
|
|
40
|
+
path: () => "/api/me",
|
|
41
|
+
body: (input) => input,
|
|
42
|
+
parseOutput: () => ({ ok: true }),
|
|
43
|
+
parseError: parseApiError,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const mintToken: Endpoint<void, TokenMintResponse, ApiError> = {
|
|
47
|
+
method: "GET",
|
|
48
|
+
path: () => "/api/auth/token",
|
|
49
|
+
parseOutput: (data) => data as TokenMintResponse,
|
|
50
|
+
parseError: parseApiError,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const refreshToken: Endpoint<
|
|
54
|
+
{
|
|
55
|
+
refreshToken: string;
|
|
56
|
+
/**
|
|
57
|
+
* The expiring access token — lets the rotation carry its active
|
|
58
|
+
* organization forward (membership re-validated server-side).
|
|
59
|
+
*/
|
|
60
|
+
accessToken?: string;
|
|
61
|
+
},
|
|
62
|
+
TokenRefreshResponse,
|
|
63
|
+
ApiError
|
|
64
|
+
> = {
|
|
65
|
+
method: "POST",
|
|
66
|
+
path: () => "/api/auth/refresh",
|
|
67
|
+
body: (input) => input,
|
|
68
|
+
parseOutput: (data) => data as TokenRefreshResponse,
|
|
69
|
+
parseError: parseApiError,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Cookie-based refresh — no body needed, the httpOnly cookie carries the token. */
|
|
73
|
+
export const refreshViaCookie: Endpoint<void, TokenRefreshResponse, ApiError> =
|
|
74
|
+
{
|
|
75
|
+
method: "POST",
|
|
76
|
+
path: () => "/api/auth/refresh",
|
|
77
|
+
parseOutput: (data) => data as TokenRefreshResponse,
|
|
78
|
+
parseError: parseApiError,
|
|
79
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
import type {
|
|
5
|
+
AuthSuccess,
|
|
6
|
+
PasskeyItem,
|
|
7
|
+
WebAuthnCeremonyStart,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* WebAuthn ceremonies are two round-trips: `start` returns the options
|
|
12
|
+
* JSON for `navigator.credentials.create()/get()` plus an opaque
|
|
13
|
+
* `challenge` handle; `finish` echoes the handle with the credential
|
|
14
|
+
* the browser produced (serialised with base64url fields — see the
|
|
15
|
+
* webauthn helpers in `@nexys/user-management-web`).
|
|
16
|
+
*/
|
|
17
|
+
export const passkeyRegisterStart: Endpoint<void, WebAuthnCeremonyStart, ApiError> = {
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: () => "/api/auth/passkey/register/start",
|
|
20
|
+
parseOutput: (data) => data as WebAuthnCeremonyStart,
|
|
21
|
+
parseError: parseApiError,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const passkeyRegisterFinish: Endpoint<
|
|
25
|
+
{ challenge: string; response: Record<string, unknown>; name?: string },
|
|
26
|
+
{ id: string },
|
|
27
|
+
ApiError
|
|
28
|
+
> = {
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: () => "/api/auth/passkey/register/finish",
|
|
31
|
+
body: (input) => input,
|
|
32
|
+
parseOutput: (data) => data as { id: string },
|
|
33
|
+
parseError: parseApiError,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Anonymous. With `email`, `allowCredentials` is scoped to that user's
|
|
38
|
+
* authenticators; without it the browser offers discoverable
|
|
39
|
+
* credentials.
|
|
40
|
+
*/
|
|
41
|
+
export const passkeySignInStart: Endpoint<
|
|
42
|
+
{ email?: string },
|
|
43
|
+
WebAuthnCeremonyStart,
|
|
44
|
+
ApiError
|
|
45
|
+
> = {
|
|
46
|
+
method: "POST",
|
|
47
|
+
path: () => "/api/auth/passkey/sign-in/start",
|
|
48
|
+
body: (input) => input,
|
|
49
|
+
parseOutput: (data) => data as WebAuthnCeremonyStart,
|
|
50
|
+
parseError: parseApiError,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const passkeySignInFinish: Endpoint<
|
|
54
|
+
{ challenge: string; response: Record<string, unknown> },
|
|
55
|
+
AuthSuccess,
|
|
56
|
+
ApiError
|
|
57
|
+
> = {
|
|
58
|
+
method: "POST",
|
|
59
|
+
path: () => "/api/auth/passkey/sign-in/finish",
|
|
60
|
+
body: (input) => input,
|
|
61
|
+
parseOutput: (data) => data as AuthSuccess,
|
|
62
|
+
parseError: parseApiError,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const listPasskeys: Endpoint<void, { passkeys: PasskeyItem[] }, ApiError> = {
|
|
66
|
+
method: "GET",
|
|
67
|
+
path: () => "/api/auth/passkey/list",
|
|
68
|
+
parseOutput: (data) => data as { passkeys: PasskeyItem[] },
|
|
69
|
+
parseError: parseApiError,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const deleteOwnPasskey: Endpoint<{ id: string }, { ok: true }, ApiError> = {
|
|
73
|
+
method: "DELETE",
|
|
74
|
+
path: (input) => `/api/auth/passkey/${input.id}`,
|
|
75
|
+
parseOutput: () => ({ ok: true }),
|
|
76
|
+
parseError: parseApiError,
|
|
77
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
import type { ProjectRow, TenancyMode, TenantRow } from "../types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Projects — isolated user-management spaces. One account can run
|
|
8
|
+
* several independent products, each as its own project with its own
|
|
9
|
+
* tenancy configuration:
|
|
10
|
+
*
|
|
11
|
+
* - `tenancyMode` — `"multi"` (any number of tenants) or `"single"`
|
|
12
|
+
* (exactly one; a second create returns `409 single_tenant_limit`).
|
|
13
|
+
* - `allowTenantCreation` — when true, any authenticated user may
|
|
14
|
+
* create a tenant inside the project (self-serve).
|
|
15
|
+
* - `allowSignup` — when true, project-scoped sign-up
|
|
16
|
+
* (`signUp` with a `projectId`) is open.
|
|
17
|
+
*/
|
|
18
|
+
export const createProject: Endpoint<
|
|
19
|
+
{
|
|
20
|
+
name: string;
|
|
21
|
+
slug?: string;
|
|
22
|
+
tenancyMode?: TenancyMode;
|
|
23
|
+
allowTenantCreation?: boolean;
|
|
24
|
+
allowSignup?: boolean;
|
|
25
|
+
metadata?: Record<string, unknown> | null;
|
|
26
|
+
},
|
|
27
|
+
ProjectRow,
|
|
28
|
+
ApiError
|
|
29
|
+
> = {
|
|
30
|
+
method: "POST",
|
|
31
|
+
path: () => "/api/projects",
|
|
32
|
+
body: (input) => input,
|
|
33
|
+
parseOutput: (data) => data as ProjectRow,
|
|
34
|
+
parseError: parseApiError,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The caller's projects, tenant counts included (superadmin sees all). */
|
|
38
|
+
export const listProjects: Endpoint<void, { projects: ProjectRow[] }, ApiError> = {
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: () => "/api/projects",
|
|
41
|
+
parseOutput: (data) => data as { projects: ProjectRow[] },
|
|
42
|
+
parseError: parseApiError,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const getProject: Endpoint<{ projectId: string }, ProjectRow, ApiError> = {
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: (input) => `/api/projects/${encodeURIComponent(input.projectId)}`,
|
|
48
|
+
parseOutput: (data) => data as ProjectRow,
|
|
49
|
+
parseError: parseApiError,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Update project settings. Omitted fields keep their current value;
|
|
54
|
+
* `metadata: null` clears the blob. Narrowing `tenancyMode` to
|
|
55
|
+
* `"single"` is refused (`409 project_has_multiple_tenants`) while the
|
|
56
|
+
* project holds more than one tenant. Owner or superadmin only.
|
|
57
|
+
*/
|
|
58
|
+
export const updateProject: Endpoint<
|
|
59
|
+
{
|
|
60
|
+
projectId: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
slug?: string;
|
|
63
|
+
tenancyMode?: TenancyMode;
|
|
64
|
+
allowTenantCreation?: boolean;
|
|
65
|
+
allowSignup?: boolean;
|
|
66
|
+
metadata?: Record<string, unknown> | null;
|
|
67
|
+
},
|
|
68
|
+
ProjectRow,
|
|
69
|
+
ApiError
|
|
70
|
+
> = {
|
|
71
|
+
method: "PATCH",
|
|
72
|
+
path: (input) => `/api/projects/${encodeURIComponent(input.projectId)}`,
|
|
73
|
+
body: ({ projectId: _projectId, ...rest }) => rest,
|
|
74
|
+
parseOutput: (data) => data as ProjectRow,
|
|
75
|
+
parseError: parseApiError,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Delete a project and every tenant inside it (cascade). */
|
|
79
|
+
export const deleteProject: Endpoint<
|
|
80
|
+
{ projectId: string },
|
|
81
|
+
{ ok: true; id: string },
|
|
82
|
+
ApiError
|
|
83
|
+
> = {
|
|
84
|
+
method: "DELETE",
|
|
85
|
+
path: (input) => `/api/projects/${encodeURIComponent(input.projectId)}`,
|
|
86
|
+
parseOutput: (data) => data as { ok: true; id: string },
|
|
87
|
+
parseError: parseApiError,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Tenants inside a project, member counts included. Owner or superadmin. */
|
|
91
|
+
export const listProjectTenants: Endpoint<
|
|
92
|
+
{ projectId: string },
|
|
93
|
+
{ tenants: TenantRow[] },
|
|
94
|
+
ApiError
|
|
95
|
+
> = {
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: (input) => `/api/projects/${encodeURIComponent(input.projectId)}/tenants`,
|
|
98
|
+
parseOutput: (data) => data as { tenants: TenantRow[] },
|
|
99
|
+
parseError: parseApiError,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create a tenant inside a project; the caller becomes its owner. The
|
|
104
|
+
* project owner (or a superadmin) always may; other authenticated users
|
|
105
|
+
* need the project's `allowTenantCreation` setting.
|
|
106
|
+
*/
|
|
107
|
+
export const createProjectTenant: Endpoint<
|
|
108
|
+
{ projectId: string; name: string; slug?: string },
|
|
109
|
+
TenantRow,
|
|
110
|
+
ApiError
|
|
111
|
+
> = {
|
|
112
|
+
method: "POST",
|
|
113
|
+
path: (input) => `/api/projects/${encodeURIComponent(input.projectId)}/tenants`,
|
|
114
|
+
body: ({ projectId: _projectId, ...rest }) => rest,
|
|
115
|
+
parseOutput: (data) => data as TenantRow,
|
|
116
|
+
parseError: parseApiError,
|
|
117
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Endpoint } from "../endpoint.js";
|
|
2
|
+
import type { ApiError } from "../result.js";
|
|
3
|
+
import { parseApiError } from "../result.js";
|
|
4
|
+
import type { RefreshToken } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export const listRefreshTokens: Endpoint<void, RefreshToken[], ApiError> = {
|
|
7
|
+
method: "GET",
|
|
8
|
+
path: () => "/api/me/refresh-tokens",
|
|
9
|
+
parseOutput: (data) => (data ?? []) as RefreshToken[],
|
|
10
|
+
parseError: parseApiError,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const revokeRefreshToken: Endpoint<{ id: string }, { ok: true }, ApiError> = {
|
|
14
|
+
method: "DELETE",
|
|
15
|
+
path: ({ id }) => `/api/me/refresh-tokens/${encodeURIComponent(id)}`,
|
|
16
|
+
parseOutput: () => ({ ok: true }),
|
|
17
|
+
parseError: parseApiError,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const revokeAllRefreshTokens: Endpoint<void, { revoked: number }, ApiError> = {
|
|
21
|
+
method: "DELETE",
|
|
22
|
+
path: () => "/api/me/refresh-tokens",
|
|
23
|
+
parseOutput: (data) => (data ?? { revoked: 0 }) as { revoked: number },
|
|
24
|
+
parseError: parseApiError,
|
|
25
|
+
};
|