convex-zen 0.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/dist/cli/generate.d.ts +14 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +297 -0
- package/dist/cli/generate.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +111 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +300 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +434 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/plugins/admin.d.ts +92 -0
- package/dist/client/plugins/admin.d.ts.map +1 -0
- package/dist/client/plugins/admin.js +165 -0
- package/dist/client/plugins/admin.js.map +1 -0
- package/dist/client/primitives.d.ts +57 -0
- package/dist/client/primitives.d.ts.map +1 -0
- package/dist/client/primitives.js +64 -0
- package/dist/client/primitives.js.map +1 -0
- package/dist/client/providers.d.ts +14 -0
- package/dist/client/providers.d.ts.map +1 -0
- package/dist/client/providers.js +25 -0
- package/dist/client/providers.js.map +1 -0
- package/dist/client/react.d.ts +23 -0
- package/dist/client/react.d.ts.map +1 -0
- package/dist/client/react.js +48 -0
- package/dist/client/react.js.map +1 -0
- package/dist/client/tanstack-start-client-plugins.d.ts +34 -0
- package/dist/client/tanstack-start-client-plugins.d.ts.map +1 -0
- package/dist/client/tanstack-start-client-plugins.js +32 -0
- package/dist/client/tanstack-start-client-plugins.js.map +1 -0
- package/dist/client/tanstack-start-client.d.ts +52 -0
- package/dist/client/tanstack-start-client.d.ts.map +1 -0
- package/dist/client/tanstack-start-client.js +130 -0
- package/dist/client/tanstack-start-client.js.map +1 -0
- package/dist/client/tanstack-start-plugins.d.ts +27 -0
- package/dist/client/tanstack-start-plugins.d.ts.map +1 -0
- package/dist/client/tanstack-start-plugins.js +145 -0
- package/dist/client/tanstack-start-plugins.js.map +1 -0
- package/dist/client/tanstack-start.d.ts +130 -0
- package/dist/client/tanstack-start.d.ts.map +1 -0
- package/dist/client/tanstack-start.js +331 -0
- package/dist/client/tanstack-start.js.map +1 -0
- package/dist/component/_generated/api.d.ts +50 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +92 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +4 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/core/sessions.d.ts +33 -0
- package/dist/component/core/sessions.d.ts.map +1 -0
- package/dist/component/core/sessions.js +186 -0
- package/dist/component/core/sessions.js.map +1 -0
- package/dist/component/core/users.d.ts +19 -0
- package/dist/component/core/users.d.ts.map +1 -0
- package/dist/component/core/users.js +154 -0
- package/dist/component/core/users.js.map +1 -0
- package/dist/component/core/verifications.d.ts +34 -0
- package/dist/component/core/verifications.d.ts.map +1 -0
- package/dist/component/core/verifications.js +135 -0
- package/dist/component/core/verifications.js.map +1 -0
- package/dist/component/gateway.d.ts +16 -0
- package/dist/component/gateway.d.ts.map +1 -0
- package/dist/component/gateway.js +229 -0
- package/dist/component/gateway.js.map +1 -0
- package/dist/component/lib/crypto.d.ts +24 -0
- package/dist/component/lib/crypto.d.ts.map +1 -0
- package/dist/component/lib/crypto.js +57 -0
- package/dist/component/lib/crypto.js.map +1 -0
- package/dist/component/lib/rateLimit.d.ts +26 -0
- package/dist/component/lib/rateLimit.d.ts.map +1 -0
- package/dist/component/lib/rateLimit.js +96 -0
- package/dist/component/lib/rateLimit.js.map +1 -0
- package/dist/component/lib/validators.d.ts +19 -0
- package/dist/component/lib/validators.d.ts.map +1 -0
- package/dist/component/lib/validators.js +12 -0
- package/dist/component/lib/validators.js.map +1 -0
- package/dist/component/plugins/admin.d.ts +72 -0
- package/dist/component/plugins/admin.d.ts.map +1 -0
- package/dist/component/plugins/admin.js +152 -0
- package/dist/component/plugins/admin.js.map +1 -0
- package/dist/component/providers/emailPassword.d.ts +49 -0
- package/dist/component/providers/emailPassword.d.ts.map +1 -0
- package/dist/component/providers/emailPassword.js +316 -0
- package/dist/component/providers/emailPassword.js.map +1 -0
- package/dist/component/providers/oauth.d.ts +33 -0
- package/dist/component/providers/oauth.d.ts.map +1 -0
- package/dist/component/providers/oauth.js +256 -0
- package/dist/component/providers/oauth.js.map +1 -0
- package/dist/component/schema.d.ts +132 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +82 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +121 -0
- package/src/cli/generate.ts +360 -0
- package/src/cli/index.ts +133 -0
- package/src/client/index.ts +707 -0
- package/src/client/plugins/admin.ts +205 -0
- package/src/client/primitives.ts +100 -0
- package/src/client/providers.ts +35 -0
- package/src/client/react.ts +97 -0
- package/src/client/tanstack-start-client-plugins.ts +113 -0
- package/src/client/tanstack-start-client.ts +259 -0
- package/src/client/tanstack-start-plugins.ts +203 -0
- package/src/client/tanstack-start.ts +535 -0
- package/src/component/_generated/api.ts +70 -0
- package/src/component/_generated/component.ts +184 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +5 -0
- package/src/component/core/sessions.ts +228 -0
- package/src/component/core/users.ts +199 -0
- package/src/component/core/verifications.ts +173 -0
- package/src/component/gateway.ts +321 -0
- package/src/component/lib/crypto.ts +63 -0
- package/src/component/lib/internalApi.ts +66 -0
- package/src/component/lib/rateLimit.ts +111 -0
- package/src/component/lib/validators.ts +12 -0
- package/src/component/plugins/admin.ts +178 -0
- package/src/component/providers/emailPassword.ts +374 -0
- package/src/component/providers/oauth.ts +324 -0
- package/src/component/schema.ts +88 -0
- package/src/types.ts +68 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { SessionInfo, SignInInput } from "./primitives";
|
|
2
|
+
import type { ReactAuthClient } from "./react";
|
|
3
|
+
|
|
4
|
+
export interface TanStackStartAuthServerFns {
|
|
5
|
+
getSession: () => Promise<SessionInfo | null>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface AuthApiErrorPayload {
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface SessionResponsePayload {
|
|
13
|
+
session?: SessionInfo | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type UnionToIntersection<T> = (
|
|
17
|
+
T extends unknown ? (arg: T) => void : never
|
|
18
|
+
) extends (arg: infer I) => void
|
|
19
|
+
? I
|
|
20
|
+
: never;
|
|
21
|
+
|
|
22
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
23
|
+
|
|
24
|
+
type PluginExtension<TPlugin> = TPlugin extends TanStackStartAuthApiClientPlugin<
|
|
25
|
+
infer TExtension
|
|
26
|
+
>
|
|
27
|
+
? TExtension
|
|
28
|
+
: never;
|
|
29
|
+
|
|
30
|
+
type PluginExtensions<
|
|
31
|
+
TPlugins extends readonly TanStackStartAuthApiClientPlugin<object>[],
|
|
32
|
+
> = [TPlugins[number]] extends [never]
|
|
33
|
+
? {}
|
|
34
|
+
: Simplify<UnionToIntersection<PluginExtension<TPlugins[number]>>>;
|
|
35
|
+
|
|
36
|
+
interface RequestFailureContext {
|
|
37
|
+
fallback: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface TanStackStartAuthApiClientPluginContext {
|
|
41
|
+
basePath: string;
|
|
42
|
+
credentials: RequestCredentials;
|
|
43
|
+
requestJson: <T>(
|
|
44
|
+
path: string,
|
|
45
|
+
init: RequestInit,
|
|
46
|
+
failure: RequestFailureContext
|
|
47
|
+
) => Promise<T>;
|
|
48
|
+
requestVoid: (
|
|
49
|
+
path: string,
|
|
50
|
+
init: RequestInit,
|
|
51
|
+
failure: RequestFailureContext
|
|
52
|
+
) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TanStackStartAuthApiClientPlugin<TExtension extends object> {
|
|
56
|
+
id: string;
|
|
57
|
+
create: (context: TanStackStartAuthApiClientPluginContext) => TExtension;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TanStackStartAuthApiClientOptions<
|
|
61
|
+
TPlugins extends readonly TanStackStartAuthApiClientPlugin<object>[] = readonly TanStackStartAuthApiClientPlugin<object>[],
|
|
62
|
+
> {
|
|
63
|
+
basePath?: string;
|
|
64
|
+
credentials?: RequestCredentials;
|
|
65
|
+
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
66
|
+
plugins?: TPlugins;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TanStackStartAuthApiClient extends ReactAuthClient {
|
|
70
|
+
signInWithEmail: (input: SignInInput) => Promise<SessionInfo>;
|
|
71
|
+
signOut: () => Promise<void>;
|
|
72
|
+
signIn: {
|
|
73
|
+
email: (input: SignInInput) => Promise<SessionInfo>;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type TanStackStartAuthApiClientWithPlugins<
|
|
78
|
+
TPlugins extends readonly TanStackStartAuthApiClientPlugin<object>[],
|
|
79
|
+
> = TanStackStartAuthApiClient & PluginExtensions<TPlugins>;
|
|
80
|
+
|
|
81
|
+
function normalizeBasePath(path: string): string {
|
|
82
|
+
const normalized = path.trim();
|
|
83
|
+
if (normalized === "") {
|
|
84
|
+
return "/api/auth";
|
|
85
|
+
}
|
|
86
|
+
const withLeadingSlash = normalized.startsWith("/")
|
|
87
|
+
? normalized
|
|
88
|
+
: `/${normalized}`;
|
|
89
|
+
if (withLeadingSlash.length > 1 && withLeadingSlash.endsWith("/")) {
|
|
90
|
+
return withLeadingSlash.slice(0, -1);
|
|
91
|
+
}
|
|
92
|
+
return withLeadingSlash;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function readJson(response: Response): Promise<unknown> {
|
|
96
|
+
try {
|
|
97
|
+
return await response.json();
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function errorMessage(
|
|
104
|
+
response: Response,
|
|
105
|
+
payload: unknown,
|
|
106
|
+
fallback: string
|
|
107
|
+
): string {
|
|
108
|
+
if (
|
|
109
|
+
payload &&
|
|
110
|
+
typeof payload === "object" &&
|
|
111
|
+
"error" in payload &&
|
|
112
|
+
typeof (payload as AuthApiErrorPayload).error === "string"
|
|
113
|
+
) {
|
|
114
|
+
return (payload as AuthApiErrorPayload).error as string;
|
|
115
|
+
}
|
|
116
|
+
return `${fallback} (${response.status})`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function toSignInBody(input: SignInInput): Record<string, string> {
|
|
120
|
+
const body: Record<string, string> = {
|
|
121
|
+
email: input.email,
|
|
122
|
+
password: input.password,
|
|
123
|
+
};
|
|
124
|
+
if (input.ipAddress) {
|
|
125
|
+
body.ipAddress = input.ipAddress;
|
|
126
|
+
}
|
|
127
|
+
if (input.userAgent) {
|
|
128
|
+
body.userAgent = input.userAgent;
|
|
129
|
+
}
|
|
130
|
+
return body;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build a ConvexZen React auth client from TanStack Start server functions.
|
|
135
|
+
*/
|
|
136
|
+
export function createTanStackStartReactAuthClient(
|
|
137
|
+
serverFns: TanStackStartAuthServerFns
|
|
138
|
+
): ReactAuthClient {
|
|
139
|
+
return {
|
|
140
|
+
getSession: async () => {
|
|
141
|
+
return await serverFns.getSession();
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build a browser auth client targeting TanStack Start auth API routes.
|
|
148
|
+
*
|
|
149
|
+
* Defaults to:
|
|
150
|
+
* - basePath: `/api/auth`
|
|
151
|
+
* - credentials: `same-origin`
|
|
152
|
+
*/
|
|
153
|
+
export function createTanStackStartAuthApiClient<
|
|
154
|
+
TPlugins extends readonly TanStackStartAuthApiClientPlugin<object>[] = [],
|
|
155
|
+
>(
|
|
156
|
+
options: TanStackStartAuthApiClientOptions<TPlugins> = {}
|
|
157
|
+
): TanStackStartAuthApiClientWithPlugins<TPlugins> {
|
|
158
|
+
const basePath = normalizeBasePath(options.basePath ?? "/api/auth");
|
|
159
|
+
const credentials = options.credentials ?? "same-origin";
|
|
160
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
161
|
+
const plugins = options.plugins ?? [];
|
|
162
|
+
|
|
163
|
+
const request = async (
|
|
164
|
+
path: string,
|
|
165
|
+
init: RequestInit,
|
|
166
|
+
failure: RequestFailureContext
|
|
167
|
+
): Promise<Response> => {
|
|
168
|
+
const response = await fetchImpl(`${basePath}/${path}`, {
|
|
169
|
+
...init,
|
|
170
|
+
credentials,
|
|
171
|
+
});
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
const payload = await readJson(response);
|
|
174
|
+
throw new Error(errorMessage(response, payload, failure.fallback));
|
|
175
|
+
}
|
|
176
|
+
return response;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const requestJson = async <T>(
|
|
180
|
+
path: string,
|
|
181
|
+
init: RequestInit,
|
|
182
|
+
failure: RequestFailureContext
|
|
183
|
+
): Promise<T> => {
|
|
184
|
+
const response = await request(path, init, failure);
|
|
185
|
+
const payload = await readJson(response);
|
|
186
|
+
return payload as T;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const requestVoid = async (
|
|
190
|
+
path: string,
|
|
191
|
+
init: RequestInit,
|
|
192
|
+
failure: RequestFailureContext
|
|
193
|
+
): Promise<void> => {
|
|
194
|
+
await request(path, init, failure);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const getSession = async (): Promise<SessionInfo | null> => {
|
|
198
|
+
const payload = await requestJson<SessionResponsePayload | null>(
|
|
199
|
+
"session",
|
|
200
|
+
{
|
|
201
|
+
method: "GET",
|
|
202
|
+
},
|
|
203
|
+
{ fallback: "Could not load session" }
|
|
204
|
+
);
|
|
205
|
+
return payload?.session ?? null;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const signInWithEmail = async (input: SignInInput): Promise<SessionInfo> => {
|
|
209
|
+
const payload = await requestJson<SessionResponsePayload | null>(
|
|
210
|
+
"sign-in-with-email",
|
|
211
|
+
{
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: { "content-type": "application/json" },
|
|
214
|
+
body: JSON.stringify(toSignInBody(input)),
|
|
215
|
+
},
|
|
216
|
+
{ fallback: "Sign in failed" }
|
|
217
|
+
);
|
|
218
|
+
if (!payload?.session) {
|
|
219
|
+
throw new Error("Sign in succeeded but no session was returned");
|
|
220
|
+
}
|
|
221
|
+
return payload.session;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const signOut = async (): Promise<void> => {
|
|
225
|
+
await requestVoid(
|
|
226
|
+
"sign-out",
|
|
227
|
+
{
|
|
228
|
+
method: "POST",
|
|
229
|
+
},
|
|
230
|
+
{ fallback: "Sign out failed" }
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const baseClient: TanStackStartAuthApiClient = {
|
|
235
|
+
getSession,
|
|
236
|
+
signInWithEmail,
|
|
237
|
+
signOut,
|
|
238
|
+
signIn: {
|
|
239
|
+
email: signInWithEmail,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const pluginContext: TanStackStartAuthApiClientPluginContext = {
|
|
244
|
+
basePath,
|
|
245
|
+
credentials,
|
|
246
|
+
requestJson,
|
|
247
|
+
requestVoid,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const pluginExtensions: Record<string, unknown> = {};
|
|
251
|
+
for (const plugin of plugins) {
|
|
252
|
+
Object.assign(pluginExtensions, plugin.create(pluginContext));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
...baseClient,
|
|
257
|
+
...pluginExtensions,
|
|
258
|
+
} as TanStackStartAuthApiClientWithPlugins<TPlugins>;
|
|
259
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { FunctionArgs, FunctionReference } from "convex/server";
|
|
2
|
+
import type { TanStackStartAuthApiPluginFactory } from "./tanstack-start";
|
|
3
|
+
|
|
4
|
+
type ActionRef = FunctionReference<"action", "public">;
|
|
5
|
+
type UnknownRecord = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export interface TanStackStartAdminApiPluginActions {
|
|
8
|
+
listUsers: ActionRef;
|
|
9
|
+
banUser: ActionRef;
|
|
10
|
+
setRole: ActionRef;
|
|
11
|
+
unbanUser?: ActionRef;
|
|
12
|
+
deleteUser?: ActionRef;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TanStackStartAdminApiPluginOptions {
|
|
16
|
+
actions: TanStackStartAdminApiPluginActions;
|
|
17
|
+
routePrefix?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeRoutePrefix(prefix: string): string {
|
|
21
|
+
const trimmed = prefix.trim().replace(/^\/+|\/+$/g, "");
|
|
22
|
+
return trimmed.length > 0 ? trimmed : "admin";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isRecord(value: unknown): value is UnknownRecord {
|
|
26
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseListUsersArgs(value: unknown): {
|
|
30
|
+
limit?: number;
|
|
31
|
+
cursor?: string;
|
|
32
|
+
} | null {
|
|
33
|
+
if (value === null || value === undefined) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
if (!isRecord(value)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const args: { limit?: number; cursor?: string } = {};
|
|
40
|
+
if (value.limit !== undefined) {
|
|
41
|
+
if (typeof value.limit !== "number") {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
args.limit = value.limit;
|
|
45
|
+
}
|
|
46
|
+
if (value.cursor !== undefined) {
|
|
47
|
+
if (typeof value.cursor !== "string") {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
args.cursor = value.cursor;
|
|
51
|
+
}
|
|
52
|
+
return args;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseBanUserArgs(value: unknown): {
|
|
56
|
+
userId: string;
|
|
57
|
+
reason?: string;
|
|
58
|
+
expiresAt?: number;
|
|
59
|
+
} | null {
|
|
60
|
+
if (!isRecord(value) || typeof value.userId !== "string") {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const args: { userId: string; reason?: string; expiresAt?: number } = {
|
|
64
|
+
userId: value.userId,
|
|
65
|
+
};
|
|
66
|
+
if (value.reason !== undefined) {
|
|
67
|
+
if (typeof value.reason !== "string") {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
args.reason = value.reason;
|
|
71
|
+
}
|
|
72
|
+
if (value.expiresAt !== undefined) {
|
|
73
|
+
if (typeof value.expiresAt !== "number") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
args.expiresAt = value.expiresAt;
|
|
77
|
+
}
|
|
78
|
+
return args;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseSetRoleArgs(value: unknown): { userId: string; role: string } | null {
|
|
82
|
+
if (
|
|
83
|
+
!isRecord(value) ||
|
|
84
|
+
typeof value.userId !== "string" ||
|
|
85
|
+
typeof value.role !== "string"
|
|
86
|
+
) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
userId: value.userId,
|
|
91
|
+
role: value.role,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function parseSingleUserIdArgs(value: unknown): { userId: string } | null {
|
|
96
|
+
if (!isRecord(value) || typeof value.userId !== "string") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return { userId: value.userId };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Add admin auth API routes to TanStack Start handler.
|
|
104
|
+
*
|
|
105
|
+
* Endpoints:
|
|
106
|
+
* - POST `/api/auth/admin/list-users`
|
|
107
|
+
* - POST `/api/auth/admin/ban-user`
|
|
108
|
+
* - POST `/api/auth/admin/set-role`
|
|
109
|
+
* - POST `/api/auth/admin/unban-user` (optional)
|
|
110
|
+
* - POST `/api/auth/admin/delete-user` (optional)
|
|
111
|
+
*/
|
|
112
|
+
export function adminApiPlugin(
|
|
113
|
+
options: TanStackStartAdminApiPluginOptions
|
|
114
|
+
): TanStackStartAuthApiPluginFactory {
|
|
115
|
+
const routePrefix = normalizeRoutePrefix(options.routePrefix ?? "admin");
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
id: "admin",
|
|
119
|
+
create: ({ fetchers }) => ({
|
|
120
|
+
id: "admin",
|
|
121
|
+
handle: async (context) => {
|
|
122
|
+
const actionPrefix = `${routePrefix}/`;
|
|
123
|
+
if (!context.action.startsWith(actionPrefix)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
if (context.method !== "POST") {
|
|
127
|
+
return context.json({ error: "Method not allowed" }, 405);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const adminAction = context.action.slice(actionPrefix.length);
|
|
131
|
+
const payload = await context.readJson();
|
|
132
|
+
|
|
133
|
+
if (adminAction === "list-users") {
|
|
134
|
+
const args = parseListUsersArgs(payload);
|
|
135
|
+
if (!args) {
|
|
136
|
+
return context.json({ error: "Invalid request body" }, 400);
|
|
137
|
+
}
|
|
138
|
+
const result = await fetchers.fetchAuthAction(
|
|
139
|
+
options.actions.listUsers,
|
|
140
|
+
args as FunctionArgs<typeof options.actions.listUsers>
|
|
141
|
+
);
|
|
142
|
+
return context.json(result);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (adminAction === "ban-user") {
|
|
146
|
+
const args = parseBanUserArgs(payload);
|
|
147
|
+
if (!args) {
|
|
148
|
+
return context.json({ error: "Invalid request body" }, 400);
|
|
149
|
+
}
|
|
150
|
+
const result = await fetchers.fetchAuthAction(
|
|
151
|
+
options.actions.banUser,
|
|
152
|
+
args as FunctionArgs<typeof options.actions.banUser>
|
|
153
|
+
);
|
|
154
|
+
return context.json(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (adminAction === "set-role") {
|
|
158
|
+
const args = parseSetRoleArgs(payload);
|
|
159
|
+
if (!args) {
|
|
160
|
+
return context.json({ error: "Invalid request body" }, 400);
|
|
161
|
+
}
|
|
162
|
+
const result = await fetchers.fetchAuthAction(
|
|
163
|
+
options.actions.setRole,
|
|
164
|
+
args as FunctionArgs<typeof options.actions.setRole>
|
|
165
|
+
);
|
|
166
|
+
return context.json(result);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (adminAction === "unban-user") {
|
|
170
|
+
if (!options.actions.unbanUser) {
|
|
171
|
+
return context.json({ error: "Not found" }, 404);
|
|
172
|
+
}
|
|
173
|
+
const args = parseSingleUserIdArgs(payload);
|
|
174
|
+
if (!args) {
|
|
175
|
+
return context.json({ error: "Invalid request body" }, 400);
|
|
176
|
+
}
|
|
177
|
+
const result = await fetchers.fetchAuthAction(
|
|
178
|
+
options.actions.unbanUser,
|
|
179
|
+
args as FunctionArgs<typeof options.actions.unbanUser>
|
|
180
|
+
);
|
|
181
|
+
return context.json(result);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (adminAction === "delete-user") {
|
|
185
|
+
if (!options.actions.deleteUser) {
|
|
186
|
+
return context.json({ error: "Not found" }, 404);
|
|
187
|
+
}
|
|
188
|
+
const args = parseSingleUserIdArgs(payload);
|
|
189
|
+
if (!args) {
|
|
190
|
+
return context.json({ error: "Invalid request body" }, 400);
|
|
191
|
+
}
|
|
192
|
+
const result = await fetchers.fetchAuthAction(
|
|
193
|
+
options.actions.deleteUser,
|
|
194
|
+
args as FunctionArgs<typeof options.actions.deleteUser>
|
|
195
|
+
);
|
|
196
|
+
return context.json(result);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return null;
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
203
|
+
}
|