@fragno-dev/auth 0.0.14 → 0.0.16
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 +196 -9
- package/dist/browser/client/react.d.ts +1194 -64
- package/dist/browser/client/react.d.ts.map +1 -1
- package/dist/browser/client/react.js +1 -1
- package/dist/browser/client/react.js.map +1 -1
- package/dist/browser/client/solid.d.ts +1446 -64
- package/dist/browser/client/solid.d.ts.map +1 -1
- package/dist/browser/client/solid.js +1 -1
- package/dist/browser/client/solid.js.map +1 -1
- package/dist/browser/client/svelte.d.ts +1194 -64
- package/dist/browser/client/svelte.d.ts.map +1 -1
- package/dist/browser/client/svelte.js +1 -1
- package/dist/browser/client/svelte.js.map +1 -1
- package/dist/browser/client/vanilla.d.ts +1194 -64
- package/dist/browser/client/vanilla.d.ts.map +1 -1
- package/dist/browser/client/vanilla.js +1 -1
- package/dist/browser/client/vanilla.js.map +1 -1
- package/dist/browser/client/vue.d.ts +1150 -20
- package/dist/browser/client/vue.d.ts.map +1 -1
- package/dist/browser/client/vue.js +1 -1
- package/dist/browser/client/vue.js.map +1 -1
- package/dist/browser/index-m_5zsra2.d.ts +7141 -0
- package/dist/browser/index-m_5zsra2.d.ts.map +1 -0
- package/dist/browser/index.d.ts +2 -600
- package/dist/browser/index.js +2 -2
- package/dist/browser/src-Ck4bl2NH.js +1892 -0
- package/dist/browser/src-Ck4bl2NH.js.map +1 -0
- package/dist/node/index.d.ts +6806 -265
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +5532 -266
- package/dist/node/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -39
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/src-DNrh9CQq.js +0 -184
- package/dist/browser/src-DNrh9CQq.js.map +0 -1
|
@@ -0,0 +1,1892 @@
|
|
|
1
|
+
import { createClientBuilder } from "@fragno-dev/core/client";
|
|
2
|
+
import { defineFragment, defineRoute, defineRoutes } from "@fragno-dev/core";
|
|
3
|
+
import { atom, computed, onMount } from "nanostores";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { decodeCursor } from "@fragno-dev/db/cursor";
|
|
6
|
+
import { column, idColumn, referenceColumn, schema } from "@fragno-dev/db/schema";
|
|
7
|
+
|
|
8
|
+
//#region src/client/default-organization.ts
|
|
9
|
+
const DEFAULT_ORGANIZATION_STORAGE_KEY = "fragno-auth.default-organization-id";
|
|
10
|
+
const DEFAULT_ORGANIZATION_CHANGE_EVENT = "fragno-auth:default-organization-change";
|
|
11
|
+
const NO_ORGANIZATIONS_ERROR_MESSAGE = "Authenticated users must always have at least one organization.";
|
|
12
|
+
function getStorage(storage) {
|
|
13
|
+
if (storage != null) return storage;
|
|
14
|
+
if (typeof window === "undefined") return null;
|
|
15
|
+
try {
|
|
16
|
+
return window.localStorage;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getWindow(win) {
|
|
22
|
+
if (win != null) return win;
|
|
23
|
+
return typeof window !== "undefined" ? window : null;
|
|
24
|
+
}
|
|
25
|
+
function toNonEmptyString(value) {
|
|
26
|
+
const trimmed = value?.trim();
|
|
27
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
28
|
+
}
|
|
29
|
+
function readCurrentDefaultOrganizationId(storage) {
|
|
30
|
+
const s = getStorage(storage);
|
|
31
|
+
if (!s) return null;
|
|
32
|
+
return toNonEmptyString(s.getItem(getDefaultOrganizationStorageKey()));
|
|
33
|
+
}
|
|
34
|
+
function getDefaultOrganizationStorageKey(_accountId) {
|
|
35
|
+
return DEFAULT_ORGANIZATION_STORAGE_KEY;
|
|
36
|
+
}
|
|
37
|
+
function getDefaultOrganizationChangeEventName(_accountId) {
|
|
38
|
+
return DEFAULT_ORGANIZATION_CHANGE_EVENT;
|
|
39
|
+
}
|
|
40
|
+
function readDefaultOrganizationId(_accountId, storage) {
|
|
41
|
+
const s = getStorage(storage);
|
|
42
|
+
if (!s) return null;
|
|
43
|
+
try {
|
|
44
|
+
return readCurrentDefaultOrganizationId(s);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function writeDefaultOrganizationId(_accountId, organizationId, storage, win) {
|
|
50
|
+
const trimmed = toNonEmptyString(organizationId);
|
|
51
|
+
if (!trimmed) return clearDefaultOrganizationId(_accountId, storage, win);
|
|
52
|
+
const s = getStorage(storage);
|
|
53
|
+
const storageKey = getDefaultOrganizationStorageKey();
|
|
54
|
+
const eventName = getDefaultOrganizationChangeEventName();
|
|
55
|
+
if (!s) return false;
|
|
56
|
+
if (readCurrentDefaultOrganizationId(s) === trimmed) return false;
|
|
57
|
+
try {
|
|
58
|
+
s.setItem(storageKey, trimmed);
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
getWindow(win)?.dispatchEvent(new Event(eventName));
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function clearDefaultOrganizationId(_accountId, storage, win) {
|
|
66
|
+
const s = getStorage(storage);
|
|
67
|
+
const storageKey = getDefaultOrganizationStorageKey();
|
|
68
|
+
const eventName = getDefaultOrganizationChangeEventName();
|
|
69
|
+
if (!s) return false;
|
|
70
|
+
if (!readCurrentDefaultOrganizationId(s)) return false;
|
|
71
|
+
try {
|
|
72
|
+
s.removeItem(storageKey);
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
getWindow(win)?.dispatchEvent(new Event(eventName));
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function subscribeToDefaultOrganizationPreference(_accountId, onChange, options) {
|
|
80
|
+
const win = getWindow(options?.windowObject);
|
|
81
|
+
const storageKey = getDefaultOrganizationStorageKey();
|
|
82
|
+
const eventName = getDefaultOrganizationChangeEventName();
|
|
83
|
+
if (!win) return () => {};
|
|
84
|
+
const handleChange = () => onChange();
|
|
85
|
+
const handleStorage = (e) => {
|
|
86
|
+
if (typeof StorageEvent === "undefined") return onChange();
|
|
87
|
+
if (e instanceof StorageEvent && (!e.key || e.key === storageKey)) onChange();
|
|
88
|
+
};
|
|
89
|
+
win.addEventListener(eventName, handleChange);
|
|
90
|
+
win.addEventListener("storage", handleStorage);
|
|
91
|
+
return () => {
|
|
92
|
+
win.removeEventListener(eventName, handleChange);
|
|
93
|
+
win.removeEventListener("storage", handleStorage);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function findOrganizationEntry(me, organizationId) {
|
|
97
|
+
const id = toNonEmptyString(organizationId);
|
|
98
|
+
if (!id) return null;
|
|
99
|
+
return me.organizations.find((e) => e.organization.id === id) ?? null;
|
|
100
|
+
}
|
|
101
|
+
function toResolution(entry, status, storedId) {
|
|
102
|
+
return {
|
|
103
|
+
status,
|
|
104
|
+
storedOrganizationId: storedId,
|
|
105
|
+
resolvedOrganizationId: entry.organization.id,
|
|
106
|
+
entry,
|
|
107
|
+
organization: entry.organization,
|
|
108
|
+
member: entry.member
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function resolveDefaultOrganization(me, storedOrganizationId) {
|
|
112
|
+
if (me.organizations.length === 0) throw new Error(NO_ORGANIZATIONS_ERROR_MESSAGE);
|
|
113
|
+
const stored = findOrganizationEntry(me, storedOrganizationId);
|
|
114
|
+
if (stored) return toResolution(stored, "reused", storedOrganizationId);
|
|
115
|
+
const active = findOrganizationEntry(me, me.activeOrganization?.organization.id ?? null);
|
|
116
|
+
const fallback = active ?? me.organizations[0];
|
|
117
|
+
return toResolution(fallback, storedOrganizationId ? "repaired" : "initialized", storedOrganizationId);
|
|
118
|
+
}
|
|
119
|
+
function syncDefaultOrganizationPreference(_accountId, me, storage, win) {
|
|
120
|
+
const resolution = resolveDefaultOrganization(me, readDefaultOrganizationId(_accountId, storage));
|
|
121
|
+
if (resolution.status !== "reused") writeDefaultOrganizationId(_accountId, resolution.resolvedOrganizationId, storage, win);
|
|
122
|
+
return resolution;
|
|
123
|
+
}
|
|
124
|
+
function setDefaultOrganizationForMe(_accountId, me, organizationId, storage, win) {
|
|
125
|
+
const entry = findOrganizationEntry(me, organizationId);
|
|
126
|
+
if (!entry) throw new Error("The selected organization is no longer available for this account.");
|
|
127
|
+
writeDefaultOrganizationId(_accountId, entry.organization.id, storage, win);
|
|
128
|
+
return resolveDefaultOrganization(me, entry.organization.id);
|
|
129
|
+
}
|
|
130
|
+
function createDefaultOrganizationPreferenceState(options) {
|
|
131
|
+
const { meStore, readMe, storage, windowObject } = options;
|
|
132
|
+
const storageVersion = atom(0);
|
|
133
|
+
const refresh = () => storageVersion.set(storageVersion.get() + 1);
|
|
134
|
+
const getCurrentStorageKey = () => getDefaultOrganizationStorageKey();
|
|
135
|
+
const storedOrganizationId = computed(storageVersion, () => readDefaultOrganizationId(null, storage));
|
|
136
|
+
const write = (id) => {
|
|
137
|
+
const ok = writeDefaultOrganizationId(null, id, storage, windowObject);
|
|
138
|
+
if (ok) refresh();
|
|
139
|
+
return ok;
|
|
140
|
+
};
|
|
141
|
+
const clear = () => {
|
|
142
|
+
const ok = clearDefaultOrganizationId(null, storage, windowObject);
|
|
143
|
+
if (ok) refresh();
|
|
144
|
+
return ok;
|
|
145
|
+
};
|
|
146
|
+
const syncForMe = (me) => {
|
|
147
|
+
const resolution$1 = syncDefaultOrganizationPreference(null, me, storage, windowObject);
|
|
148
|
+
refresh();
|
|
149
|
+
return resolution$1;
|
|
150
|
+
};
|
|
151
|
+
const setForMe = (me, id) => {
|
|
152
|
+
const entry = findOrganizationEntry(me, id);
|
|
153
|
+
if (!entry) throw new Error("The selected organization is no longer available for this account.");
|
|
154
|
+
const resolution$1 = setDefaultOrganizationForMe(null, me, id, storage, windowObject);
|
|
155
|
+
refresh();
|
|
156
|
+
return resolution$1;
|
|
157
|
+
};
|
|
158
|
+
onMount(storedOrganizationId, () => subscribeToDefaultOrganizationPreference(null, refresh, { windowObject }));
|
|
159
|
+
const resolution = computed([meStore, storedOrganizationId], (meState, orgId) => {
|
|
160
|
+
if (!meState.data) return null;
|
|
161
|
+
return resolveDefaultOrganization(meState.data, orgId);
|
|
162
|
+
});
|
|
163
|
+
onMount(resolution, () => resolution.listen((next) => {
|
|
164
|
+
if (!next || next.status === "reused") return;
|
|
165
|
+
if (writeDefaultOrganizationId(null, next.resolvedOrganizationId, storage, windowObject)) refresh();
|
|
166
|
+
}));
|
|
167
|
+
const effectiveMe = computed([meStore, resolution], (meState) => meState.data ?? null);
|
|
168
|
+
return {
|
|
169
|
+
me: async (params) => {
|
|
170
|
+
const me = await readMe(params);
|
|
171
|
+
if (getWindow(windowObject)) syncForMe(me);
|
|
172
|
+
return me;
|
|
173
|
+
},
|
|
174
|
+
defaultOrganization: {
|
|
175
|
+
get storageKey() {
|
|
176
|
+
return getCurrentStorageKey();
|
|
177
|
+
},
|
|
178
|
+
read: () => readDefaultOrganizationId(null, storage),
|
|
179
|
+
write,
|
|
180
|
+
clear,
|
|
181
|
+
resolve: resolveDefaultOrganization,
|
|
182
|
+
sync: syncForMe,
|
|
183
|
+
setForMe
|
|
184
|
+
},
|
|
185
|
+
store: {
|
|
186
|
+
get storageKey() {
|
|
187
|
+
return getCurrentStorageKey();
|
|
188
|
+
},
|
|
189
|
+
storedOrganizationId,
|
|
190
|
+
resolution,
|
|
191
|
+
status: computed(resolution, (r) => r?.status ?? null),
|
|
192
|
+
defaultOrganizationId: computed(resolution, (r) => r?.resolvedOrganizationId ?? null),
|
|
193
|
+
defaultOrganization: computed(resolution, (r) => r?.entry ?? null),
|
|
194
|
+
me: effectiveMe,
|
|
195
|
+
loading: computed(meStore, (s) => s.loading),
|
|
196
|
+
error: computed(meStore, (s) => s.error),
|
|
197
|
+
readDefaultOrganizationId: () => readDefaultOrganizationId(null, storage),
|
|
198
|
+
writeDefaultOrganizationId: write,
|
|
199
|
+
clearDefaultOrganizationId: clear,
|
|
200
|
+
syncPreference: () => {
|
|
201
|
+
const me = effectiveMe.get();
|
|
202
|
+
return me ? syncForMe(me) : null;
|
|
203
|
+
},
|
|
204
|
+
setDefaultOrganization: (id) => {
|
|
205
|
+
const me = effectiveMe.get();
|
|
206
|
+
if (!me) throw new Error("Cannot set a default organization without an authenticated user.");
|
|
207
|
+
return setForMe(me, id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/oauth/routes.ts
|
|
215
|
+
const oauthRoutesFactory = defineRoutes().create(({ services, config }) => {
|
|
216
|
+
return [defineRoute({
|
|
217
|
+
method: "GET",
|
|
218
|
+
path: "/oauth/:provider/authorize",
|
|
219
|
+
queryParameters: [
|
|
220
|
+
"redirectUri",
|
|
221
|
+
"returnTo",
|
|
222
|
+
"link",
|
|
223
|
+
"sessionId",
|
|
224
|
+
"scope",
|
|
225
|
+
"loginHint",
|
|
226
|
+
"session"
|
|
227
|
+
],
|
|
228
|
+
outputSchema: z.object({ url: z.string() }),
|
|
229
|
+
errorCodes: [
|
|
230
|
+
"oauth_disabled",
|
|
231
|
+
"provider_not_found",
|
|
232
|
+
"missing_redirect_uri",
|
|
233
|
+
"redirect_uri_mismatch",
|
|
234
|
+
"invalid_input",
|
|
235
|
+
"session_invalid"
|
|
236
|
+
],
|
|
237
|
+
handler: () => {}
|
|
238
|
+
}), defineRoute({
|
|
239
|
+
method: "GET",
|
|
240
|
+
path: "/oauth/:provider/callback",
|
|
241
|
+
queryParameters: [
|
|
242
|
+
"code",
|
|
243
|
+
"state",
|
|
244
|
+
"requestSignUp"
|
|
245
|
+
],
|
|
246
|
+
outputSchema: z.object({
|
|
247
|
+
sessionId: z.string(),
|
|
248
|
+
userId: z.string(),
|
|
249
|
+
email: z.string(),
|
|
250
|
+
role: z.enum(["user", "admin"]),
|
|
251
|
+
returnTo: z.string().nullable()
|
|
252
|
+
}),
|
|
253
|
+
errorCodes: [
|
|
254
|
+
"oauth_disabled",
|
|
255
|
+
"provider_not_found",
|
|
256
|
+
"missing_redirect_uri",
|
|
257
|
+
"invalid_code",
|
|
258
|
+
"invalid_state",
|
|
259
|
+
"email_required",
|
|
260
|
+
"signup_disabled",
|
|
261
|
+
"signup_required",
|
|
262
|
+
"user_banned"
|
|
263
|
+
],
|
|
264
|
+
handler: () => {}
|
|
265
|
+
})];
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/utils/cookie.ts
|
|
270
|
+
/**
|
|
271
|
+
* Cookie utilities for session management
|
|
272
|
+
*/
|
|
273
|
+
const COOKIE_NAME = "sessionid";
|
|
274
|
+
/**
|
|
275
|
+
* Parse cookies from a Cookie header string
|
|
276
|
+
*/
|
|
277
|
+
function parseCookies(cookieHeader) {
|
|
278
|
+
if (!cookieHeader) return {};
|
|
279
|
+
const cookies = {};
|
|
280
|
+
const pairs = cookieHeader.split(";");
|
|
281
|
+
for (const pair of pairs) {
|
|
282
|
+
const [key, ...valueParts] = pair.split("=");
|
|
283
|
+
const trimmedKey = key?.trim();
|
|
284
|
+
const value = valueParts.join("=").trim();
|
|
285
|
+
if (trimmedKey) cookies[trimmedKey] = decodeURIComponent(value);
|
|
286
|
+
}
|
|
287
|
+
return cookies;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Extract session ID from headers, checking cookies first, then query/body
|
|
291
|
+
*/
|
|
292
|
+
function extractSessionId(headers, queryParam, bodySessionId) {
|
|
293
|
+
const cookieHeader = headers.get("Cookie");
|
|
294
|
+
const cookies = parseCookies(cookieHeader);
|
|
295
|
+
const sessionIdFromCookie = cookies[COOKIE_NAME];
|
|
296
|
+
if (sessionIdFromCookie) return sessionIdFromCookie;
|
|
297
|
+
if (queryParam) return queryParam;
|
|
298
|
+
if (bodySessionId) return bodySessionId;
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/organization/schemas.ts
|
|
304
|
+
const organizationSchema = z.object({
|
|
305
|
+
id: z.string(),
|
|
306
|
+
name: z.string(),
|
|
307
|
+
slug: z.string(),
|
|
308
|
+
logoUrl: z.string().nullable(),
|
|
309
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
310
|
+
createdBy: z.string(),
|
|
311
|
+
createdAt: z.string(),
|
|
312
|
+
updatedAt: z.string(),
|
|
313
|
+
deletedAt: z.string().nullable()
|
|
314
|
+
});
|
|
315
|
+
const memberSchema = z.object({
|
|
316
|
+
id: z.string(),
|
|
317
|
+
organizationId: z.string(),
|
|
318
|
+
userId: z.string(),
|
|
319
|
+
roles: z.array(z.string()),
|
|
320
|
+
createdAt: z.string(),
|
|
321
|
+
updatedAt: z.string()
|
|
322
|
+
});
|
|
323
|
+
const invitationSchema = z.object({
|
|
324
|
+
id: z.string(),
|
|
325
|
+
organizationId: z.string(),
|
|
326
|
+
email: z.string(),
|
|
327
|
+
roles: z.array(z.string()),
|
|
328
|
+
status: z.enum([
|
|
329
|
+
"pending",
|
|
330
|
+
"accepted",
|
|
331
|
+
"rejected",
|
|
332
|
+
"canceled",
|
|
333
|
+
"expired"
|
|
334
|
+
]),
|
|
335
|
+
token: z.string(),
|
|
336
|
+
inviterId: z.string(),
|
|
337
|
+
expiresAt: z.string(),
|
|
338
|
+
createdAt: z.string(),
|
|
339
|
+
respondedAt: z.string().nullable()
|
|
340
|
+
});
|
|
341
|
+
const invitationSummarySchema = invitationSchema.omit({ token: true });
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/organization/serializers.ts
|
|
345
|
+
const normalizeMetadata = (value) => {
|
|
346
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
347
|
+
return value;
|
|
348
|
+
};
|
|
349
|
+
function serializeOrganization(organization) {
|
|
350
|
+
return {
|
|
351
|
+
id: organization.id,
|
|
352
|
+
name: organization.name,
|
|
353
|
+
slug: organization.slug,
|
|
354
|
+
logoUrl: organization.logoUrl ?? null,
|
|
355
|
+
metadata: normalizeMetadata(organization.metadata),
|
|
356
|
+
createdBy: organization.createdBy,
|
|
357
|
+
createdAt: organization.createdAt.toISOString(),
|
|
358
|
+
updatedAt: organization.updatedAt.toISOString(),
|
|
359
|
+
deletedAt: organization.deletedAt ? organization.deletedAt.toISOString() : null
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function serializeMember(member) {
|
|
363
|
+
return {
|
|
364
|
+
id: member.id,
|
|
365
|
+
organizationId: member.organizationId,
|
|
366
|
+
userId: member.userId,
|
|
367
|
+
roles: member.roles,
|
|
368
|
+
createdAt: member.createdAt.toISOString(),
|
|
369
|
+
updatedAt: member.updatedAt.toISOString()
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function serializeInvitation(invitation) {
|
|
373
|
+
return {
|
|
374
|
+
id: invitation.id,
|
|
375
|
+
organizationId: invitation.organizationId,
|
|
376
|
+
email: invitation.email,
|
|
377
|
+
roles: invitation.roles,
|
|
378
|
+
status: invitation.status,
|
|
379
|
+
token: invitation.token,
|
|
380
|
+
inviterId: invitation.inviterId,
|
|
381
|
+
expiresAt: invitation.expiresAt.toISOString(),
|
|
382
|
+
createdAt: invitation.createdAt.toISOString(),
|
|
383
|
+
respondedAt: invitation.respondedAt ? invitation.respondedAt.toISOString() : null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/organization/routes.ts
|
|
389
|
+
const createOrganizationInputSchema = z.object({
|
|
390
|
+
name: z.string().min(1).max(120),
|
|
391
|
+
slug: z.string().min(1),
|
|
392
|
+
logoUrl: z.string().nullable().optional(),
|
|
393
|
+
metadata: z.record(z.string(), z.unknown()).nullable().optional()
|
|
394
|
+
});
|
|
395
|
+
const updateOrganizationInputSchema = z.object({
|
|
396
|
+
name: z.string().min(1).max(120).optional(),
|
|
397
|
+
slug: z.string().min(1).optional(),
|
|
398
|
+
logoUrl: z.string().nullable().optional(),
|
|
399
|
+
metadata: z.record(z.string(), z.unknown()).nullable().optional()
|
|
400
|
+
}).refine((value) => Object.keys(value).length > 0, { message: "At least one field must be provided" });
|
|
401
|
+
const pageQuerySchema = z.object({ pageSize: z.coerce.number().int().min(1).max(100).default(20) });
|
|
402
|
+
const parseCursor = (cursorParam) => {
|
|
403
|
+
if (!cursorParam) return void 0;
|
|
404
|
+
try {
|
|
405
|
+
return decodeCursor(cursorParam);
|
|
406
|
+
} catch {
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const organizationRoutesFactory = defineRoutes().create(({ config, services }) => {
|
|
411
|
+
const defineOrganizationRoute = (route) => defineRoute({
|
|
412
|
+
...route,
|
|
413
|
+
errorCodes: route.errorCodes ? Array.from(new Set([...route.errorCodes, "feature_disabled"])) : ["feature_disabled"],
|
|
414
|
+
handler: () => {}
|
|
415
|
+
});
|
|
416
|
+
return [
|
|
417
|
+
defineOrganizationRoute({
|
|
418
|
+
method: "POST",
|
|
419
|
+
path: "/organizations",
|
|
420
|
+
inputSchema: createOrganizationInputSchema,
|
|
421
|
+
outputSchema: z.object({
|
|
422
|
+
organization: organizationSchema,
|
|
423
|
+
member: memberSchema
|
|
424
|
+
}),
|
|
425
|
+
errorCodes: [
|
|
426
|
+
"invalid_input",
|
|
427
|
+
"organization_slug_taken",
|
|
428
|
+
"permission_denied",
|
|
429
|
+
"limit_reached",
|
|
430
|
+
"session_invalid"
|
|
431
|
+
],
|
|
432
|
+
handler: async function({ input, headers, query }, { json, error }) {
|
|
433
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
434
|
+
if (!sessionId) return error({
|
|
435
|
+
message: "Session ID required",
|
|
436
|
+
code: "session_invalid"
|
|
437
|
+
}, 400);
|
|
438
|
+
let body = null;
|
|
439
|
+
let inputError = null;
|
|
440
|
+
try {
|
|
441
|
+
body = await input.valid();
|
|
442
|
+
} catch (err) {
|
|
443
|
+
inputError = err;
|
|
444
|
+
}
|
|
445
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationWithSession({
|
|
446
|
+
sessionId,
|
|
447
|
+
input: body,
|
|
448
|
+
inputError
|
|
449
|
+
})]).execute();
|
|
450
|
+
if (!result.ok) {
|
|
451
|
+
if (result.code === "session_invalid") return error({
|
|
452
|
+
message: "Invalid session",
|
|
453
|
+
code: "session_invalid"
|
|
454
|
+
}, 401);
|
|
455
|
+
if (result.code === "input_invalid") {
|
|
456
|
+
if (inputError) throw inputError;
|
|
457
|
+
return error({
|
|
458
|
+
message: "Invalid input",
|
|
459
|
+
code: "invalid_input"
|
|
460
|
+
}, 400);
|
|
461
|
+
}
|
|
462
|
+
if (result.code === "organization_slug_taken") return error({
|
|
463
|
+
message: "Organization slug taken",
|
|
464
|
+
code: "organization_slug_taken"
|
|
465
|
+
}, 400);
|
|
466
|
+
if (result.code === "invalid_slug") return error({
|
|
467
|
+
message: "Invalid input",
|
|
468
|
+
code: "invalid_input"
|
|
469
|
+
}, 400);
|
|
470
|
+
if (result.code === "limit_reached") return error({
|
|
471
|
+
message: "Limit reached",
|
|
472
|
+
code: "limit_reached"
|
|
473
|
+
}, 400);
|
|
474
|
+
return error({
|
|
475
|
+
message: "Permission denied",
|
|
476
|
+
code: "permission_denied"
|
|
477
|
+
}, 403);
|
|
478
|
+
}
|
|
479
|
+
return json({
|
|
480
|
+
organization: serializeOrganization(result.organization),
|
|
481
|
+
member: serializeMember(result.member)
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}),
|
|
485
|
+
defineOrganizationRoute({
|
|
486
|
+
method: "GET",
|
|
487
|
+
path: "/organizations",
|
|
488
|
+
queryParameters: [
|
|
489
|
+
"pageSize",
|
|
490
|
+
"cursor",
|
|
491
|
+
"sessionId"
|
|
492
|
+
],
|
|
493
|
+
outputSchema: z.object({
|
|
494
|
+
organizations: z.array(z.object({
|
|
495
|
+
organization: organizationSchema,
|
|
496
|
+
member: memberSchema
|
|
497
|
+
})),
|
|
498
|
+
cursor: z.string().optional(),
|
|
499
|
+
hasNextPage: z.boolean()
|
|
500
|
+
}),
|
|
501
|
+
errorCodes: ["invalid_input", "session_invalid"],
|
|
502
|
+
handler: async function({ query, headers }, { json, error }) {
|
|
503
|
+
const parsed = pageQuerySchema.safeParse(Object.fromEntries(query.entries()));
|
|
504
|
+
if (!parsed.success) return error({
|
|
505
|
+
message: "Invalid query parameters",
|
|
506
|
+
code: "invalid_input"
|
|
507
|
+
}, 400);
|
|
508
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
509
|
+
if (!sessionId) return error({
|
|
510
|
+
message: "Session ID required",
|
|
511
|
+
code: "session_invalid"
|
|
512
|
+
}, 400);
|
|
513
|
+
const rawCursor = parseCursor(query.get("cursor"));
|
|
514
|
+
const cursor = rawCursor && (rawCursor.indexName === "_primary" || rawCursor.indexName === "primary") ? rawCursor : void 0;
|
|
515
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.getOrganizationsForSession({
|
|
516
|
+
sessionId,
|
|
517
|
+
pageSize: parsed.data.pageSize,
|
|
518
|
+
cursor
|
|
519
|
+
})]).execute();
|
|
520
|
+
if (!result.ok) return error({
|
|
521
|
+
message: "Invalid session",
|
|
522
|
+
code: "session_invalid"
|
|
523
|
+
}, 401);
|
|
524
|
+
return json({
|
|
525
|
+
organizations: result.organizations.map((entry) => ({
|
|
526
|
+
organization: serializeOrganization(entry.organization),
|
|
527
|
+
member: serializeMember(entry.member)
|
|
528
|
+
})),
|
|
529
|
+
cursor: result.cursor,
|
|
530
|
+
hasNextPage: result.hasNextPage
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}),
|
|
534
|
+
defineOrganizationRoute({
|
|
535
|
+
method: "GET",
|
|
536
|
+
path: "/organizations/active",
|
|
537
|
+
queryParameters: ["sessionId"],
|
|
538
|
+
outputSchema: z.object({
|
|
539
|
+
organization: organizationSchema,
|
|
540
|
+
member: memberSchema
|
|
541
|
+
}).nullable(),
|
|
542
|
+
errorCodes: ["session_invalid"],
|
|
543
|
+
handler: async function({ headers, query }, { json, error }) {
|
|
544
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
545
|
+
if (!sessionId) return error({
|
|
546
|
+
message: "Session ID required",
|
|
547
|
+
code: "session_invalid"
|
|
548
|
+
}, 400);
|
|
549
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.getActiveOrganizationForSession({ sessionId })]).execute();
|
|
550
|
+
if (!result.ok) return error({
|
|
551
|
+
message: "Invalid session",
|
|
552
|
+
code: "session_invalid"
|
|
553
|
+
}, 401);
|
|
554
|
+
if (!result.data) return json(null);
|
|
555
|
+
return json({
|
|
556
|
+
organization: serializeOrganization(result.data.organization),
|
|
557
|
+
member: serializeMember(result.data.member)
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}),
|
|
561
|
+
defineOrganizationRoute({
|
|
562
|
+
method: "POST",
|
|
563
|
+
path: "/organizations/active",
|
|
564
|
+
queryParameters: ["sessionId"],
|
|
565
|
+
inputSchema: z.object({ organizationId: z.string() }),
|
|
566
|
+
outputSchema: z.object({
|
|
567
|
+
organization: organizationSchema,
|
|
568
|
+
member: memberSchema
|
|
569
|
+
}),
|
|
570
|
+
errorCodes: [
|
|
571
|
+
"organization_not_found",
|
|
572
|
+
"membership_not_found",
|
|
573
|
+
"session_invalid"
|
|
574
|
+
],
|
|
575
|
+
handler: async function({ input, headers, query }, { json, error }) {
|
|
576
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
577
|
+
if (!sessionId) return error({
|
|
578
|
+
message: "Session ID required",
|
|
579
|
+
code: "session_invalid"
|
|
580
|
+
}, 400);
|
|
581
|
+
const body = await input.valid();
|
|
582
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.setActiveOrganizationForSession({
|
|
583
|
+
sessionId,
|
|
584
|
+
organizationId: body.organizationId
|
|
585
|
+
})]).execute();
|
|
586
|
+
if (!result.ok) {
|
|
587
|
+
if (result.code === "organization_not_found") return error({
|
|
588
|
+
message: "Organization not found",
|
|
589
|
+
code: "organization_not_found"
|
|
590
|
+
}, 404);
|
|
591
|
+
if (result.code === "membership_not_found") return error({
|
|
592
|
+
message: "Membership not found",
|
|
593
|
+
code: "membership_not_found"
|
|
594
|
+
}, 404);
|
|
595
|
+
return error({
|
|
596
|
+
message: "Invalid session",
|
|
597
|
+
code: "session_invalid"
|
|
598
|
+
}, 401);
|
|
599
|
+
}
|
|
600
|
+
return json({
|
|
601
|
+
organization: serializeOrganization(result.organization),
|
|
602
|
+
member: serializeMember(result.member)
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}),
|
|
606
|
+
defineOrganizationRoute({
|
|
607
|
+
method: "GET",
|
|
608
|
+
path: "/organizations/invitations",
|
|
609
|
+
queryParameters: ["sessionId"],
|
|
610
|
+
outputSchema: z.object({ invitations: z.array(z.object({
|
|
611
|
+
invitation: invitationSchema,
|
|
612
|
+
organization: organizationSchema
|
|
613
|
+
})) }),
|
|
614
|
+
errorCodes: ["session_invalid"],
|
|
615
|
+
handler: async function({ headers, query }, { json, error }) {
|
|
616
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
617
|
+
if (!sessionId) return error({
|
|
618
|
+
message: "Session ID required",
|
|
619
|
+
code: "session_invalid"
|
|
620
|
+
}, 400);
|
|
621
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.listInvitationsForSession({ sessionId })]).execute();
|
|
622
|
+
if (!result.ok) return error({
|
|
623
|
+
message: "Invalid session",
|
|
624
|
+
code: "session_invalid"
|
|
625
|
+
}, 401);
|
|
626
|
+
return json({ invitations: result.invitations.map((entry) => ({
|
|
627
|
+
invitation: serializeInvitation(entry.invitation),
|
|
628
|
+
organization: serializeOrganization(entry.organization)
|
|
629
|
+
})) });
|
|
630
|
+
}
|
|
631
|
+
}),
|
|
632
|
+
defineOrganizationRoute({
|
|
633
|
+
method: "PATCH",
|
|
634
|
+
path: "/organizations/invitations/:invitationId",
|
|
635
|
+
queryParameters: ["sessionId"],
|
|
636
|
+
inputSchema: z.object({
|
|
637
|
+
action: z.enum([
|
|
638
|
+
"accept",
|
|
639
|
+
"reject",
|
|
640
|
+
"cancel"
|
|
641
|
+
]),
|
|
642
|
+
token: z.string().optional()
|
|
643
|
+
}),
|
|
644
|
+
outputSchema: z.object({ invitation: invitationSchema }),
|
|
645
|
+
errorCodes: [
|
|
646
|
+
"invitation_not_found",
|
|
647
|
+
"invitation_expired",
|
|
648
|
+
"permission_denied",
|
|
649
|
+
"invalid_token",
|
|
650
|
+
"limit_reached",
|
|
651
|
+
"session_invalid"
|
|
652
|
+
],
|
|
653
|
+
handler: async function({ input, pathParams, headers, query }, { json, error }) {
|
|
654
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
655
|
+
if (!sessionId) return error({
|
|
656
|
+
message: "Session ID required",
|
|
657
|
+
code: "session_invalid"
|
|
658
|
+
}, 400);
|
|
659
|
+
const body = await input.valid();
|
|
660
|
+
if ((body.action === "accept" || body.action === "reject") && !body.token) return error({
|
|
661
|
+
message: "Invalid token",
|
|
662
|
+
code: "invalid_token"
|
|
663
|
+
}, 400);
|
|
664
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.respondToInvitationWithSession({
|
|
665
|
+
sessionId,
|
|
666
|
+
invitationId: pathParams.invitationId,
|
|
667
|
+
action: body.action,
|
|
668
|
+
token: body.token
|
|
669
|
+
})]).execute();
|
|
670
|
+
if (!result.ok) {
|
|
671
|
+
if (result.code === "invalid_token") return error({
|
|
672
|
+
message: "Invalid token",
|
|
673
|
+
code: "invalid_token"
|
|
674
|
+
}, 400);
|
|
675
|
+
if (result.code === "invitation_expired") return error({
|
|
676
|
+
message: "Invitation expired",
|
|
677
|
+
code: "invitation_expired"
|
|
678
|
+
}, 400);
|
|
679
|
+
if (result.code === "limit_reached") return error({
|
|
680
|
+
message: "Limit reached",
|
|
681
|
+
code: "limit_reached"
|
|
682
|
+
}, 400);
|
|
683
|
+
if (result.code === "permission_denied") return error({
|
|
684
|
+
message: "Permission denied",
|
|
685
|
+
code: "permission_denied"
|
|
686
|
+
}, 403);
|
|
687
|
+
if (result.code === "session_invalid") return error({
|
|
688
|
+
message: "Invalid session",
|
|
689
|
+
code: "session_invalid"
|
|
690
|
+
}, 401);
|
|
691
|
+
return error({
|
|
692
|
+
message: "Invitation not found",
|
|
693
|
+
code: "invitation_not_found"
|
|
694
|
+
}, 404);
|
|
695
|
+
}
|
|
696
|
+
return json({ invitation: serializeInvitation(result.invitation) });
|
|
697
|
+
}
|
|
698
|
+
}),
|
|
699
|
+
defineOrganizationRoute({
|
|
700
|
+
method: "GET",
|
|
701
|
+
path: "/organizations/:organizationId",
|
|
702
|
+
queryParameters: ["sessionId"],
|
|
703
|
+
outputSchema: z.object({
|
|
704
|
+
organization: organizationSchema,
|
|
705
|
+
member: memberSchema
|
|
706
|
+
}),
|
|
707
|
+
errorCodes: [
|
|
708
|
+
"organization_not_found",
|
|
709
|
+
"permission_denied",
|
|
710
|
+
"session_invalid"
|
|
711
|
+
],
|
|
712
|
+
handler: async function({ pathParams, headers, query }, { json, error }) {
|
|
713
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
714
|
+
if (!sessionId) return error({
|
|
715
|
+
message: "Session ID required",
|
|
716
|
+
code: "session_invalid"
|
|
717
|
+
}, 400);
|
|
718
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.getOrganizationForSession({
|
|
719
|
+
sessionId,
|
|
720
|
+
organizationId: pathParams.organizationId
|
|
721
|
+
})]).execute();
|
|
722
|
+
if (!result.ok) {
|
|
723
|
+
if (result.code === "organization_not_found") return error({
|
|
724
|
+
message: "Organization not found",
|
|
725
|
+
code: "organization_not_found"
|
|
726
|
+
}, 404);
|
|
727
|
+
if (result.code === "permission_denied") return error({
|
|
728
|
+
message: "Permission denied",
|
|
729
|
+
code: "permission_denied"
|
|
730
|
+
}, 403);
|
|
731
|
+
return error({
|
|
732
|
+
message: "Invalid session",
|
|
733
|
+
code: "session_invalid"
|
|
734
|
+
}, 401);
|
|
735
|
+
}
|
|
736
|
+
return json({
|
|
737
|
+
organization: serializeOrganization(result.organization),
|
|
738
|
+
member: serializeMember(result.member)
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}),
|
|
742
|
+
defineOrganizationRoute({
|
|
743
|
+
method: "PATCH",
|
|
744
|
+
path: "/organizations/:organizationId",
|
|
745
|
+
queryParameters: ["sessionId"],
|
|
746
|
+
inputSchema: updateOrganizationInputSchema,
|
|
747
|
+
outputSchema: z.object({ organization: organizationSchema }),
|
|
748
|
+
errorCodes: [
|
|
749
|
+
"invalid_input",
|
|
750
|
+
"organization_not_found",
|
|
751
|
+
"organization_slug_taken",
|
|
752
|
+
"permission_denied",
|
|
753
|
+
"session_invalid"
|
|
754
|
+
],
|
|
755
|
+
handler: async function({ input, pathParams, headers, query }, { json, error }) {
|
|
756
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
757
|
+
if (!sessionId) return error({
|
|
758
|
+
message: "Session ID required",
|
|
759
|
+
code: "session_invalid"
|
|
760
|
+
}, 400);
|
|
761
|
+
const body = await input.valid();
|
|
762
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.updateOrganizationWithSession({
|
|
763
|
+
sessionId,
|
|
764
|
+
organizationId: pathParams.organizationId,
|
|
765
|
+
patch: body
|
|
766
|
+
})]).execute();
|
|
767
|
+
if (!result.ok) {
|
|
768
|
+
if (result.code === "session_invalid") return error({
|
|
769
|
+
message: "Invalid session",
|
|
770
|
+
code: "session_invalid"
|
|
771
|
+
}, 401);
|
|
772
|
+
if (result.code === "organization_not_found") return error({
|
|
773
|
+
message: "Organization not found",
|
|
774
|
+
code: "organization_not_found"
|
|
775
|
+
}, 404);
|
|
776
|
+
if (result.code === "organization_slug_taken") return error({
|
|
777
|
+
message: "Organization slug taken",
|
|
778
|
+
code: "organization_slug_taken"
|
|
779
|
+
}, 400);
|
|
780
|
+
if (result.code === "permission_denied") return error({
|
|
781
|
+
message: "Permission denied",
|
|
782
|
+
code: "permission_denied"
|
|
783
|
+
}, 403);
|
|
784
|
+
return error({
|
|
785
|
+
message: "Invalid input",
|
|
786
|
+
code: "invalid_input"
|
|
787
|
+
}, 400);
|
|
788
|
+
}
|
|
789
|
+
return json({ organization: serializeOrganization(result.organization) });
|
|
790
|
+
}
|
|
791
|
+
}),
|
|
792
|
+
defineOrganizationRoute({
|
|
793
|
+
method: "DELETE",
|
|
794
|
+
path: "/organizations/:organizationId",
|
|
795
|
+
queryParameters: ["sessionId"],
|
|
796
|
+
outputSchema: z.object({ success: z.boolean() }),
|
|
797
|
+
errorCodes: [
|
|
798
|
+
"organization_not_found",
|
|
799
|
+
"permission_denied",
|
|
800
|
+
"session_invalid"
|
|
801
|
+
],
|
|
802
|
+
handler: async function({ pathParams, headers, query }, { json, error }) {
|
|
803
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
804
|
+
if (!sessionId) return error({
|
|
805
|
+
message: "Session ID required",
|
|
806
|
+
code: "session_invalid"
|
|
807
|
+
}, 400);
|
|
808
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.deleteOrganizationWithSession({
|
|
809
|
+
sessionId,
|
|
810
|
+
organizationId: pathParams.organizationId
|
|
811
|
+
})]).execute();
|
|
812
|
+
if (!result.ok) {
|
|
813
|
+
if (result.code === "session_invalid") return error({
|
|
814
|
+
message: "Invalid session",
|
|
815
|
+
code: "session_invalid"
|
|
816
|
+
}, 401);
|
|
817
|
+
if (result.code === "organization_not_found") return error({
|
|
818
|
+
message: "Organization not found",
|
|
819
|
+
code: "organization_not_found"
|
|
820
|
+
}, 404);
|
|
821
|
+
return error({
|
|
822
|
+
message: "Permission denied",
|
|
823
|
+
code: "permission_denied"
|
|
824
|
+
}, 403);
|
|
825
|
+
}
|
|
826
|
+
return json({ success: true });
|
|
827
|
+
}
|
|
828
|
+
}),
|
|
829
|
+
defineOrganizationRoute({
|
|
830
|
+
method: "GET",
|
|
831
|
+
path: "/organizations/:organizationId/members",
|
|
832
|
+
queryParameters: [
|
|
833
|
+
"pageSize",
|
|
834
|
+
"cursor",
|
|
835
|
+
"sessionId"
|
|
836
|
+
],
|
|
837
|
+
outputSchema: z.object({
|
|
838
|
+
members: z.array(memberSchema),
|
|
839
|
+
cursor: z.string().optional(),
|
|
840
|
+
hasNextPage: z.boolean()
|
|
841
|
+
}),
|
|
842
|
+
errorCodes: [
|
|
843
|
+
"invalid_input",
|
|
844
|
+
"organization_not_found",
|
|
845
|
+
"permission_denied",
|
|
846
|
+
"session_invalid"
|
|
847
|
+
],
|
|
848
|
+
handler: async function({ pathParams, query, headers }, { json, error }) {
|
|
849
|
+
const parsed = pageQuerySchema.safeParse(Object.fromEntries(query.entries()));
|
|
850
|
+
if (!parsed.success) return error({
|
|
851
|
+
message: "Invalid query parameters",
|
|
852
|
+
code: "invalid_input"
|
|
853
|
+
}, 400);
|
|
854
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
855
|
+
if (!sessionId) return error({
|
|
856
|
+
message: "Session ID required",
|
|
857
|
+
code: "session_invalid"
|
|
858
|
+
}, 400);
|
|
859
|
+
const cursor = parseCursor(query.get("cursor"));
|
|
860
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.listOrganizationMembersWithSession({
|
|
861
|
+
sessionId,
|
|
862
|
+
organizationId: pathParams.organizationId,
|
|
863
|
+
pageSize: parsed.data.pageSize,
|
|
864
|
+
cursor
|
|
865
|
+
})]).execute();
|
|
866
|
+
if (!result.ok) {
|
|
867
|
+
if (result.code === "session_invalid") return error({
|
|
868
|
+
message: "Invalid session",
|
|
869
|
+
code: "session_invalid"
|
|
870
|
+
}, 401);
|
|
871
|
+
if (result.code === "organization_not_found") return error({
|
|
872
|
+
message: "Organization not found",
|
|
873
|
+
code: "organization_not_found"
|
|
874
|
+
}, 404);
|
|
875
|
+
return error({
|
|
876
|
+
message: "Permission denied",
|
|
877
|
+
code: "permission_denied"
|
|
878
|
+
}, 403);
|
|
879
|
+
}
|
|
880
|
+
return json({
|
|
881
|
+
members: result.members.map((member) => serializeMember(member)),
|
|
882
|
+
cursor: result.cursor,
|
|
883
|
+
hasNextPage: result.hasNextPage
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}),
|
|
887
|
+
defineOrganizationRoute({
|
|
888
|
+
method: "POST",
|
|
889
|
+
path: "/organizations/:organizationId/members",
|
|
890
|
+
queryParameters: ["sessionId"],
|
|
891
|
+
inputSchema: z.object({
|
|
892
|
+
userId: z.string(),
|
|
893
|
+
roles: z.array(z.string()).optional()
|
|
894
|
+
}),
|
|
895
|
+
outputSchema: z.object({ member: memberSchema }),
|
|
896
|
+
errorCodes: [
|
|
897
|
+
"organization_not_found",
|
|
898
|
+
"permission_denied",
|
|
899
|
+
"member_already_exists",
|
|
900
|
+
"limit_reached",
|
|
901
|
+
"session_invalid"
|
|
902
|
+
],
|
|
903
|
+
handler: async function({ input, pathParams, headers, query }, { json, error }) {
|
|
904
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
905
|
+
if (!sessionId) return error({
|
|
906
|
+
message: "Session ID required",
|
|
907
|
+
code: "session_invalid"
|
|
908
|
+
}, 400);
|
|
909
|
+
const body = await input.valid();
|
|
910
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationMemberWithSession({
|
|
911
|
+
sessionId,
|
|
912
|
+
organizationId: pathParams.organizationId,
|
|
913
|
+
userId: body.userId,
|
|
914
|
+
roles: body.roles
|
|
915
|
+
})]).execute();
|
|
916
|
+
if (!result.ok) {
|
|
917
|
+
if (result.code === "session_invalid") return error({
|
|
918
|
+
message: "Invalid session",
|
|
919
|
+
code: "session_invalid"
|
|
920
|
+
}, 401);
|
|
921
|
+
if (result.code === "organization_not_found") return error({
|
|
922
|
+
message: "Organization not found",
|
|
923
|
+
code: "organization_not_found"
|
|
924
|
+
}, 404);
|
|
925
|
+
if (result.code === "member_already_exists") return error({
|
|
926
|
+
message: "Member already exists",
|
|
927
|
+
code: "member_already_exists"
|
|
928
|
+
}, 400);
|
|
929
|
+
if (result.code === "limit_reached") return error({
|
|
930
|
+
message: "Limit reached",
|
|
931
|
+
code: "limit_reached"
|
|
932
|
+
}, 400);
|
|
933
|
+
return error({
|
|
934
|
+
message: "Permission denied",
|
|
935
|
+
code: "permission_denied"
|
|
936
|
+
}, 403);
|
|
937
|
+
}
|
|
938
|
+
return json({ member: serializeMember(result.member) });
|
|
939
|
+
}
|
|
940
|
+
}),
|
|
941
|
+
defineOrganizationRoute({
|
|
942
|
+
method: "PATCH",
|
|
943
|
+
path: "/organizations/:organizationId/members/:memberId",
|
|
944
|
+
queryParameters: ["sessionId"],
|
|
945
|
+
inputSchema: z.object({ roles: z.array(z.string()).min(1) }),
|
|
946
|
+
outputSchema: z.object({ member: memberSchema }),
|
|
947
|
+
errorCodes: [
|
|
948
|
+
"member_not_found",
|
|
949
|
+
"permission_denied",
|
|
950
|
+
"last_owner",
|
|
951
|
+
"session_invalid"
|
|
952
|
+
],
|
|
953
|
+
handler: async function({ input, pathParams, headers, query }, { json, error }) {
|
|
954
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
955
|
+
if (!sessionId) return error({
|
|
956
|
+
message: "Session ID required",
|
|
957
|
+
code: "session_invalid"
|
|
958
|
+
}, 400);
|
|
959
|
+
const body = await input.valid();
|
|
960
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.updateOrganizationMemberRolesWithSession({
|
|
961
|
+
sessionId,
|
|
962
|
+
organizationId: pathParams.organizationId,
|
|
963
|
+
memberId: pathParams.memberId,
|
|
964
|
+
roles: body.roles
|
|
965
|
+
})]).execute();
|
|
966
|
+
if (!result.ok) {
|
|
967
|
+
if (result.code === "session_invalid") return error({
|
|
968
|
+
message: "Invalid session",
|
|
969
|
+
code: "session_invalid"
|
|
970
|
+
}, 401);
|
|
971
|
+
if (result.code === "member_not_found") return error({
|
|
972
|
+
message: "Member not found",
|
|
973
|
+
code: "member_not_found"
|
|
974
|
+
}, 404);
|
|
975
|
+
if (result.code === "last_owner") return error({
|
|
976
|
+
message: "Last owner",
|
|
977
|
+
code: "last_owner"
|
|
978
|
+
}, 400);
|
|
979
|
+
return error({
|
|
980
|
+
message: "Permission denied",
|
|
981
|
+
code: "permission_denied"
|
|
982
|
+
}, 403);
|
|
983
|
+
}
|
|
984
|
+
return json({ member: serializeMember(result.member) });
|
|
985
|
+
}
|
|
986
|
+
}),
|
|
987
|
+
defineOrganizationRoute({
|
|
988
|
+
method: "DELETE",
|
|
989
|
+
path: "/organizations/:organizationId/members/:memberId",
|
|
990
|
+
queryParameters: ["sessionId"],
|
|
991
|
+
outputSchema: z.object({ success: z.boolean() }),
|
|
992
|
+
errorCodes: [
|
|
993
|
+
"member_not_found",
|
|
994
|
+
"permission_denied",
|
|
995
|
+
"last_owner",
|
|
996
|
+
"session_invalid"
|
|
997
|
+
],
|
|
998
|
+
handler: async function({ pathParams, headers, query }, { json, error }) {
|
|
999
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
1000
|
+
if (!sessionId) return error({
|
|
1001
|
+
message: "Session ID required",
|
|
1002
|
+
code: "session_invalid"
|
|
1003
|
+
}, 400);
|
|
1004
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.deleteOrganizationMemberWithSession({
|
|
1005
|
+
sessionId,
|
|
1006
|
+
organizationId: pathParams.organizationId,
|
|
1007
|
+
memberId: pathParams.memberId
|
|
1008
|
+
})]).execute();
|
|
1009
|
+
if (!result.ok) {
|
|
1010
|
+
if (result.code === "session_invalid") return error({
|
|
1011
|
+
message: "Invalid session",
|
|
1012
|
+
code: "session_invalid"
|
|
1013
|
+
}, 401);
|
|
1014
|
+
if (result.code === "member_not_found") return error({
|
|
1015
|
+
message: "Member not found",
|
|
1016
|
+
code: "member_not_found"
|
|
1017
|
+
}, 404);
|
|
1018
|
+
if (result.code === "last_owner") return error({
|
|
1019
|
+
message: "Last owner",
|
|
1020
|
+
code: "last_owner"
|
|
1021
|
+
}, 400);
|
|
1022
|
+
return error({
|
|
1023
|
+
message: "Permission denied",
|
|
1024
|
+
code: "permission_denied"
|
|
1025
|
+
}, 403);
|
|
1026
|
+
}
|
|
1027
|
+
return json({ success: true });
|
|
1028
|
+
}
|
|
1029
|
+
}),
|
|
1030
|
+
defineOrganizationRoute({
|
|
1031
|
+
method: "GET",
|
|
1032
|
+
path: "/organizations/:organizationId/invitations",
|
|
1033
|
+
queryParameters: ["sessionId"],
|
|
1034
|
+
outputSchema: z.object({ invitations: z.array(invitationSchema) }),
|
|
1035
|
+
errorCodes: [
|
|
1036
|
+
"organization_not_found",
|
|
1037
|
+
"permission_denied",
|
|
1038
|
+
"session_invalid"
|
|
1039
|
+
],
|
|
1040
|
+
handler: async function({ pathParams, headers, query }, { json, error }) {
|
|
1041
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
1042
|
+
if (!sessionId) return error({
|
|
1043
|
+
message: "Session ID required",
|
|
1044
|
+
code: "session_invalid"
|
|
1045
|
+
}, 400);
|
|
1046
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.listOrganizationInvitationsWithSession({
|
|
1047
|
+
sessionId,
|
|
1048
|
+
organizationId: pathParams.organizationId
|
|
1049
|
+
})]).execute();
|
|
1050
|
+
if (!result.ok) {
|
|
1051
|
+
if (result.code === "session_invalid") return error({
|
|
1052
|
+
message: "Invalid session",
|
|
1053
|
+
code: "session_invalid"
|
|
1054
|
+
}, 401);
|
|
1055
|
+
if (result.code === "organization_not_found") return error({
|
|
1056
|
+
message: "Organization not found",
|
|
1057
|
+
code: "organization_not_found"
|
|
1058
|
+
}, 404);
|
|
1059
|
+
return error({
|
|
1060
|
+
message: "Permission denied",
|
|
1061
|
+
code: "permission_denied"
|
|
1062
|
+
}, 403);
|
|
1063
|
+
}
|
|
1064
|
+
return json({ invitations: result.invitations.map(serializeInvitation) });
|
|
1065
|
+
}
|
|
1066
|
+
}),
|
|
1067
|
+
defineOrganizationRoute({
|
|
1068
|
+
method: "POST",
|
|
1069
|
+
path: "/organizations/:organizationId/invitations",
|
|
1070
|
+
queryParameters: ["sessionId"],
|
|
1071
|
+
inputSchema: z.object({
|
|
1072
|
+
email: z.email(),
|
|
1073
|
+
roles: z.array(z.string()).optional()
|
|
1074
|
+
}),
|
|
1075
|
+
outputSchema: z.object({ invitation: invitationSchema }),
|
|
1076
|
+
errorCodes: [
|
|
1077
|
+
"organization_not_found",
|
|
1078
|
+
"permission_denied",
|
|
1079
|
+
"limit_reached",
|
|
1080
|
+
"session_invalid"
|
|
1081
|
+
],
|
|
1082
|
+
handler: async function({ input, pathParams, headers, query }, { json, error }) {
|
|
1083
|
+
const sessionId = extractSessionId(headers, query.get("sessionId"));
|
|
1084
|
+
if (!sessionId) return error({
|
|
1085
|
+
message: "Session ID required",
|
|
1086
|
+
code: "session_invalid"
|
|
1087
|
+
}, 400);
|
|
1088
|
+
const body = await input.valid();
|
|
1089
|
+
const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationInvitationWithSession({
|
|
1090
|
+
sessionId,
|
|
1091
|
+
organizationId: pathParams.organizationId,
|
|
1092
|
+
email: body.email,
|
|
1093
|
+
roles: body.roles
|
|
1094
|
+
})]).execute();
|
|
1095
|
+
if (!result.ok) {
|
|
1096
|
+
if (result.code === "session_invalid") return error({
|
|
1097
|
+
message: "Invalid session",
|
|
1098
|
+
code: "session_invalid"
|
|
1099
|
+
}, 401);
|
|
1100
|
+
if (result.code === "organization_not_found") return error({
|
|
1101
|
+
message: "Organization not found",
|
|
1102
|
+
code: "organization_not_found"
|
|
1103
|
+
}, 404);
|
|
1104
|
+
if (result.code === "limit_reached") return error({
|
|
1105
|
+
message: "Limit reached",
|
|
1106
|
+
code: "limit_reached"
|
|
1107
|
+
}, 400);
|
|
1108
|
+
return error({
|
|
1109
|
+
message: "Permission denied",
|
|
1110
|
+
code: "permission_denied"
|
|
1111
|
+
}, 403);
|
|
1112
|
+
}
|
|
1113
|
+
return json({ invitation: serializeInvitation(result.invitation) });
|
|
1114
|
+
}
|
|
1115
|
+
})
|
|
1116
|
+
];
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/schema.ts
|
|
1121
|
+
const authSchema = schema("auth", (s) => {
|
|
1122
|
+
return s.addTable("user", (t) => {
|
|
1123
|
+
return t.addColumn("id", idColumn()).addColumn("email", column("string")).addColumn("passwordHash", column("string")).addColumn("role", column("string").defaultTo("user")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_user_email", ["email"]).createIndex("idx_user_id", ["id"], { unique: true });
|
|
1124
|
+
}).addTable("session", (t) => {
|
|
1125
|
+
return t.addColumn("id", idColumn()).addColumn("userId", referenceColumn()).addColumn("expiresAt", column("timestamp")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_session_user", ["userId"]);
|
|
1126
|
+
}).alterTable("user", (t) => {
|
|
1127
|
+
return t.addColumn("bannedAt", column("timestamp").nullable()).createIndex("idx_user_createdAt", ["createdAt"]);
|
|
1128
|
+
}).alterTable("session", (t) => {
|
|
1129
|
+
return t.addColumn("activeOrganizationId", referenceColumn().nullable());
|
|
1130
|
+
}).addTable("organization", (t) => {
|
|
1131
|
+
return t.addColumn("id", idColumn()).addColumn("name", column("string")).addColumn("slug", column("string")).addColumn("logoUrl", column("string").nullable()).addColumn("metadata", column("json").nullable()).addColumn("createdBy", referenceColumn()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).addColumn("deletedAt", column("timestamp").nullable()).createIndex("idx_organization_slug", ["slug"], { unique: true }).createIndex("idx_organization_createdBy", ["createdBy"]);
|
|
1132
|
+
}).addTable("organizationMember", (t) => {
|
|
1133
|
+
return t.addColumn("id", idColumn()).addColumn("organizationId", referenceColumn()).addColumn("userId", referenceColumn()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_org_member_org_user", ["organizationId", "userId"], { unique: true }).createIndex("idx_org_member_user", ["userId"]).createIndex("idx_org_member_org", ["organizationId"]);
|
|
1134
|
+
}).addTable("organizationMemberRole", (t) => {
|
|
1135
|
+
return t.addColumn("id", idColumn()).addColumn("memberId", referenceColumn()).addColumn("role", column("string")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_org_member_role_member_role", ["memberId", "role"], { unique: true }).createIndex("idx_org_member_role_member", ["memberId"]).createIndex("idx_org_member_role_role", ["role"]);
|
|
1136
|
+
}).addTable("organizationInvitation", (t) => {
|
|
1137
|
+
return t.addColumn("id", idColumn()).addColumn("organizationId", referenceColumn()).addColumn("email", column("string")).addColumn("roles", column("json")).addColumn("status", column("string")).addColumn("token", column("string")).addColumn("inviterId", referenceColumn()).addColumn("expiresAt", column("timestamp")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("respondedAt", column("timestamp").nullable()).createIndex("idx_org_invitation_token", ["token"], { unique: true }).createIndex("idx_org_invitation_org_status", ["organizationId", "status"]).createIndex("idx_org_invitation_email", ["email"]).createIndex("idx_org_invitation_email_status", ["email", "status"]);
|
|
1138
|
+
}).addReference("sessionOwner", {
|
|
1139
|
+
from: {
|
|
1140
|
+
table: "session",
|
|
1141
|
+
column: "userId"
|
|
1142
|
+
},
|
|
1143
|
+
to: {
|
|
1144
|
+
table: "user",
|
|
1145
|
+
column: "id"
|
|
1146
|
+
},
|
|
1147
|
+
type: "one"
|
|
1148
|
+
}).addReference("sessionActiveOrganization", {
|
|
1149
|
+
from: {
|
|
1150
|
+
table: "session",
|
|
1151
|
+
column: "activeOrganizationId"
|
|
1152
|
+
},
|
|
1153
|
+
to: {
|
|
1154
|
+
table: "organization",
|
|
1155
|
+
column: "id"
|
|
1156
|
+
},
|
|
1157
|
+
type: "one"
|
|
1158
|
+
}).addReference("organizationCreator", {
|
|
1159
|
+
from: {
|
|
1160
|
+
table: "organization",
|
|
1161
|
+
column: "createdBy"
|
|
1162
|
+
},
|
|
1163
|
+
to: {
|
|
1164
|
+
table: "user",
|
|
1165
|
+
column: "id"
|
|
1166
|
+
},
|
|
1167
|
+
type: "one"
|
|
1168
|
+
}).addReference("organizationMemberOrganization", {
|
|
1169
|
+
from: {
|
|
1170
|
+
table: "organizationMember",
|
|
1171
|
+
column: "organizationId"
|
|
1172
|
+
},
|
|
1173
|
+
to: {
|
|
1174
|
+
table: "organization",
|
|
1175
|
+
column: "id"
|
|
1176
|
+
},
|
|
1177
|
+
type: "one"
|
|
1178
|
+
}).addReference("organizationMembers", {
|
|
1179
|
+
from: {
|
|
1180
|
+
table: "organization",
|
|
1181
|
+
column: "id"
|
|
1182
|
+
},
|
|
1183
|
+
to: {
|
|
1184
|
+
table: "organizationMember",
|
|
1185
|
+
column: "organizationId"
|
|
1186
|
+
},
|
|
1187
|
+
type: "many",
|
|
1188
|
+
foreignKey: false
|
|
1189
|
+
}).addReference("organizationMemberUser", {
|
|
1190
|
+
from: {
|
|
1191
|
+
table: "organizationMember",
|
|
1192
|
+
column: "userId"
|
|
1193
|
+
},
|
|
1194
|
+
to: {
|
|
1195
|
+
table: "user",
|
|
1196
|
+
column: "id"
|
|
1197
|
+
},
|
|
1198
|
+
type: "one"
|
|
1199
|
+
}).addReference("organizationMemberRoleMember", {
|
|
1200
|
+
from: {
|
|
1201
|
+
table: "organizationMemberRole",
|
|
1202
|
+
column: "memberId"
|
|
1203
|
+
},
|
|
1204
|
+
to: {
|
|
1205
|
+
table: "organizationMember",
|
|
1206
|
+
column: "id"
|
|
1207
|
+
},
|
|
1208
|
+
type: "one"
|
|
1209
|
+
}).addReference("organizationInvitationOrganization", {
|
|
1210
|
+
from: {
|
|
1211
|
+
table: "organizationInvitation",
|
|
1212
|
+
column: "organizationId"
|
|
1213
|
+
},
|
|
1214
|
+
to: {
|
|
1215
|
+
table: "organization",
|
|
1216
|
+
column: "id"
|
|
1217
|
+
},
|
|
1218
|
+
type: "one"
|
|
1219
|
+
}).addReference("organizationInvitationInviter", {
|
|
1220
|
+
from: {
|
|
1221
|
+
table: "organizationInvitation",
|
|
1222
|
+
column: "inviterId"
|
|
1223
|
+
},
|
|
1224
|
+
to: {
|
|
1225
|
+
table: "user",
|
|
1226
|
+
column: "id"
|
|
1227
|
+
},
|
|
1228
|
+
type: "one"
|
|
1229
|
+
}).addTable("oauthAccount", (t) => {
|
|
1230
|
+
return t.addColumn("id", idColumn()).addColumn("userId", referenceColumn()).addColumn("provider", column("string")).addColumn("providerAccountId", column("string")).addColumn("email", column("string").nullable()).addColumn("emailVerified", column("bool").defaultTo(false)).addColumn("image", column("string").nullable()).addColumn("accessToken", column("string").nullable()).addColumn("refreshToken", column("string").nullable()).addColumn("idToken", column("string").nullable()).addColumn("tokenType", column("string").nullable()).addColumn("tokenExpiresAt", column("timestamp").nullable()).addColumn("scopes", column("json").nullable()).addColumn("rawProfile", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_oauth_account_provider_account", ["provider", "providerAccountId"], { unique: true }).createIndex("idx_oauth_account_user", ["userId"]).createIndex("idx_oauth_account_provider", ["provider"]);
|
|
1231
|
+
}).addTable("oauthState", (t) => {
|
|
1232
|
+
return t.addColumn("id", idColumn()).addColumn("provider", column("string")).addColumn("state", column("string")).addColumn("codeVerifier", column("string").nullable()).addColumn("redirectUri", column("string").nullable()).addColumn("returnTo", column("string").nullable()).addColumn("linkUserId", referenceColumn().nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("expiresAt", column("timestamp")).createIndex("idx_oauth_state_state", ["state"], { unique: true }).createIndex("idx_oauth_state_provider", ["provider"]).createIndex("idx_oauth_state_expiresAt", ["expiresAt"]);
|
|
1233
|
+
}).addReference("oauthAccountUser", {
|
|
1234
|
+
from: {
|
|
1235
|
+
table: "oauthAccount",
|
|
1236
|
+
column: "userId"
|
|
1237
|
+
},
|
|
1238
|
+
to: {
|
|
1239
|
+
table: "user",
|
|
1240
|
+
column: "id"
|
|
1241
|
+
},
|
|
1242
|
+
type: "one"
|
|
1243
|
+
}).addReference("oauthStateLinkUser", {
|
|
1244
|
+
from: {
|
|
1245
|
+
table: "oauthState",
|
|
1246
|
+
column: "linkUserId"
|
|
1247
|
+
},
|
|
1248
|
+
to: {
|
|
1249
|
+
table: "user",
|
|
1250
|
+
column: "id"
|
|
1251
|
+
},
|
|
1252
|
+
type: "one"
|
|
1253
|
+
}).alterTable("user", (t) => {
|
|
1254
|
+
return t.alterColumn("passwordHash").nullable();
|
|
1255
|
+
}).addReference("sessionOrganizationMembers", {
|
|
1256
|
+
type: "many",
|
|
1257
|
+
from: {
|
|
1258
|
+
table: "session",
|
|
1259
|
+
column: "userId"
|
|
1260
|
+
},
|
|
1261
|
+
to: {
|
|
1262
|
+
table: "organizationMember",
|
|
1263
|
+
column: "userId"
|
|
1264
|
+
},
|
|
1265
|
+
foreignKey: false
|
|
1266
|
+
}).addReference("sessionMembers", {
|
|
1267
|
+
type: "many",
|
|
1268
|
+
from: {
|
|
1269
|
+
table: "session",
|
|
1270
|
+
column: "userId"
|
|
1271
|
+
},
|
|
1272
|
+
to: {
|
|
1273
|
+
table: "organizationMember",
|
|
1274
|
+
column: "userId"
|
|
1275
|
+
},
|
|
1276
|
+
foreignKey: false
|
|
1277
|
+
}).addReference("organizationMemberRoles", {
|
|
1278
|
+
type: "many",
|
|
1279
|
+
from: {
|
|
1280
|
+
table: "organizationMember",
|
|
1281
|
+
column: "id"
|
|
1282
|
+
},
|
|
1283
|
+
to: {
|
|
1284
|
+
table: "organizationMemberRole",
|
|
1285
|
+
column: "memberId"
|
|
1286
|
+
},
|
|
1287
|
+
foreignKey: false
|
|
1288
|
+
}).addReference("roles", {
|
|
1289
|
+
type: "many",
|
|
1290
|
+
from: {
|
|
1291
|
+
table: "organizationMember",
|
|
1292
|
+
column: "id"
|
|
1293
|
+
},
|
|
1294
|
+
to: {
|
|
1295
|
+
table: "organizationMemberRole",
|
|
1296
|
+
column: "memberId"
|
|
1297
|
+
},
|
|
1298
|
+
foreignKey: false
|
|
1299
|
+
}).addReference("organization", {
|
|
1300
|
+
type: "one",
|
|
1301
|
+
from: {
|
|
1302
|
+
table: "organizationMember",
|
|
1303
|
+
column: "organizationId"
|
|
1304
|
+
},
|
|
1305
|
+
to: {
|
|
1306
|
+
table: "organization",
|
|
1307
|
+
column: "id"
|
|
1308
|
+
},
|
|
1309
|
+
foreignKey: false
|
|
1310
|
+
}).addReference("userOrganizationInvitations", {
|
|
1311
|
+
type: "many",
|
|
1312
|
+
from: {
|
|
1313
|
+
table: "user",
|
|
1314
|
+
column: "email"
|
|
1315
|
+
},
|
|
1316
|
+
to: {
|
|
1317
|
+
table: "organizationInvitation",
|
|
1318
|
+
column: "email"
|
|
1319
|
+
},
|
|
1320
|
+
foreignKey: false
|
|
1321
|
+
}).addReference("invitations", {
|
|
1322
|
+
type: "many",
|
|
1323
|
+
from: {
|
|
1324
|
+
table: "user",
|
|
1325
|
+
column: "email"
|
|
1326
|
+
},
|
|
1327
|
+
to: {
|
|
1328
|
+
table: "organizationInvitation",
|
|
1329
|
+
column: "email"
|
|
1330
|
+
},
|
|
1331
|
+
foreignKey: false
|
|
1332
|
+
}).addReference("organization", {
|
|
1333
|
+
type: "one",
|
|
1334
|
+
from: {
|
|
1335
|
+
table: "organizationInvitation",
|
|
1336
|
+
column: "organizationId"
|
|
1337
|
+
},
|
|
1338
|
+
to: {
|
|
1339
|
+
table: "organization",
|
|
1340
|
+
column: "id"
|
|
1341
|
+
},
|
|
1342
|
+
foreignKey: false
|
|
1343
|
+
}).alterTable("session", (t) => {
|
|
1344
|
+
return t.createIndex("idx_session_id_expiresAt", ["id", "expiresAt"]);
|
|
1345
|
+
}).addReference("userOrganizationMembers", {
|
|
1346
|
+
type: "many",
|
|
1347
|
+
from: {
|
|
1348
|
+
table: "user",
|
|
1349
|
+
column: "id"
|
|
1350
|
+
},
|
|
1351
|
+
to: {
|
|
1352
|
+
table: "organizationMember",
|
|
1353
|
+
column: "userId"
|
|
1354
|
+
},
|
|
1355
|
+
foreignKey: false
|
|
1356
|
+
}).alterTable("oauthState", (t) => {
|
|
1357
|
+
return t.addColumn("sessionSeed", column("json").nullable());
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
//#endregion
|
|
1362
|
+
//#region src/session/session-seed.ts
|
|
1363
|
+
const sessionSeedSchema = z.object({ activeOrganizationId: z.string().trim().min(1).optional() }).strict();
|
|
1364
|
+
function normalizeSessionSeed(session) {
|
|
1365
|
+
const parsed = sessionSeedSchema.safeParse(session ?? {});
|
|
1366
|
+
if (!parsed.success) return null;
|
|
1367
|
+
return parsed.data.activeOrganizationId ? parsed.data : null;
|
|
1368
|
+
}
|
|
1369
|
+
function serializeSessionSeedForQuery(session) {
|
|
1370
|
+
const normalized = normalizeSessionSeed(session);
|
|
1371
|
+
return normalized ? JSON.stringify(normalized) : void 0;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
//#endregion
|
|
1375
|
+
//#region src/session/session.ts
|
|
1376
|
+
const sessionRoutesFactory = defineRoutes().create(({ services, config }) => {
|
|
1377
|
+
return [defineRoute({
|
|
1378
|
+
method: "POST",
|
|
1379
|
+
path: "/sign-out",
|
|
1380
|
+
inputSchema: z.object({ sessionId: z.string().optional() }).optional(),
|
|
1381
|
+
outputSchema: z.object({ success: z.boolean() }),
|
|
1382
|
+
errorCodes: ["session_not_found"],
|
|
1383
|
+
handler: () => {}
|
|
1384
|
+
}), defineRoute({
|
|
1385
|
+
method: "GET",
|
|
1386
|
+
path: "/me",
|
|
1387
|
+
queryParameters: ["sessionId"],
|
|
1388
|
+
outputSchema: z.object({
|
|
1389
|
+
user: z.object({
|
|
1390
|
+
id: z.string(),
|
|
1391
|
+
email: z.string(),
|
|
1392
|
+
role: z.enum(["user", "admin"])
|
|
1393
|
+
}),
|
|
1394
|
+
organizations: z.array(z.object({
|
|
1395
|
+
organization: organizationSchema,
|
|
1396
|
+
member: memberSchema
|
|
1397
|
+
})),
|
|
1398
|
+
activeOrganization: z.object({
|
|
1399
|
+
organization: organizationSchema,
|
|
1400
|
+
member: memberSchema
|
|
1401
|
+
}).nullable(),
|
|
1402
|
+
invitations: z.array(z.object({
|
|
1403
|
+
invitation: invitationSummarySchema,
|
|
1404
|
+
organization: organizationSchema
|
|
1405
|
+
}))
|
|
1406
|
+
}),
|
|
1407
|
+
errorCodes: ["session_invalid"],
|
|
1408
|
+
handler: () => {}
|
|
1409
|
+
})];
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
//#endregion
|
|
1413
|
+
//#region src/user/user-actions.ts
|
|
1414
|
+
const userActionsRoutesFactory = defineRoutes().create(({ services, config }) => {
|
|
1415
|
+
return [
|
|
1416
|
+
defineRoute({
|
|
1417
|
+
method: "PATCH",
|
|
1418
|
+
path: "/users/:userId/role",
|
|
1419
|
+
inputSchema: z.object({ role: z.enum(["user", "admin"]) }),
|
|
1420
|
+
outputSchema: z.object({ success: z.boolean() }),
|
|
1421
|
+
errorCodes: [
|
|
1422
|
+
"invalid_input",
|
|
1423
|
+
"session_invalid",
|
|
1424
|
+
"permission_denied"
|
|
1425
|
+
],
|
|
1426
|
+
handler: () => {}
|
|
1427
|
+
}),
|
|
1428
|
+
defineRoute({
|
|
1429
|
+
method: "POST",
|
|
1430
|
+
path: "/sign-up",
|
|
1431
|
+
inputSchema: z.object({
|
|
1432
|
+
email: z.email(),
|
|
1433
|
+
password: z.string().min(8).max(100)
|
|
1434
|
+
}),
|
|
1435
|
+
outputSchema: z.object({
|
|
1436
|
+
sessionId: z.string(),
|
|
1437
|
+
userId: z.string(),
|
|
1438
|
+
email: z.string(),
|
|
1439
|
+
role: z.enum(["user", "admin"])
|
|
1440
|
+
}),
|
|
1441
|
+
errorCodes: [
|
|
1442
|
+
"email_already_exists",
|
|
1443
|
+
"invalid_input",
|
|
1444
|
+
"email_password_disabled"
|
|
1445
|
+
],
|
|
1446
|
+
handler: () => {}
|
|
1447
|
+
}),
|
|
1448
|
+
defineRoute({
|
|
1449
|
+
method: "POST",
|
|
1450
|
+
path: "/sign-in",
|
|
1451
|
+
inputSchema: z.object({
|
|
1452
|
+
email: z.email(),
|
|
1453
|
+
password: z.string().min(8).max(100),
|
|
1454
|
+
session: sessionSeedSchema.optional()
|
|
1455
|
+
}),
|
|
1456
|
+
outputSchema: z.object({
|
|
1457
|
+
sessionId: z.string(),
|
|
1458
|
+
userId: z.string(),
|
|
1459
|
+
email: z.string(),
|
|
1460
|
+
role: z.enum(["user", "admin"])
|
|
1461
|
+
}),
|
|
1462
|
+
errorCodes: [
|
|
1463
|
+
"invalid_credentials",
|
|
1464
|
+
"user_banned",
|
|
1465
|
+
"email_password_disabled"
|
|
1466
|
+
],
|
|
1467
|
+
handler: () => {}
|
|
1468
|
+
}),
|
|
1469
|
+
defineRoute({
|
|
1470
|
+
method: "POST",
|
|
1471
|
+
path: "/change-password",
|
|
1472
|
+
inputSchema: z.object({ newPassword: z.string().min(8).max(100) }),
|
|
1473
|
+
outputSchema: z.object({ success: z.boolean() }),
|
|
1474
|
+
errorCodes: ["session_invalid"],
|
|
1475
|
+
handler: () => {}
|
|
1476
|
+
})
|
|
1477
|
+
];
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/user/user-overview.ts
|
|
1482
|
+
const sortBySchema = z.enum(["email", "createdAt"]);
|
|
1483
|
+
const userOverviewRoutesFactory = defineRoutes().create(({ services }) => {
|
|
1484
|
+
return [defineRoute({
|
|
1485
|
+
method: "GET",
|
|
1486
|
+
path: "/users",
|
|
1487
|
+
queryParameters: [
|
|
1488
|
+
"search",
|
|
1489
|
+
"sortBy",
|
|
1490
|
+
"sortOrder",
|
|
1491
|
+
"pageSize",
|
|
1492
|
+
"cursor"
|
|
1493
|
+
],
|
|
1494
|
+
outputSchema: z.object({
|
|
1495
|
+
users: z.array(z.object({
|
|
1496
|
+
id: z.string(),
|
|
1497
|
+
email: z.string(),
|
|
1498
|
+
role: z.enum(["user", "admin"]),
|
|
1499
|
+
createdAt: z.string()
|
|
1500
|
+
})),
|
|
1501
|
+
cursor: z.string().optional(),
|
|
1502
|
+
hasNextPage: z.boolean(),
|
|
1503
|
+
sortBy: sortBySchema
|
|
1504
|
+
}),
|
|
1505
|
+
errorCodes: ["invalid_input"],
|
|
1506
|
+
handler: () => {}
|
|
1507
|
+
})];
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
//#endregion
|
|
1511
|
+
//#region src/oauth/utils.ts
|
|
1512
|
+
const DEFAULT_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
1513
|
+
const createAuthorizationURL = (params) => {
|
|
1514
|
+
const url = new URL(params.authorizationEndpoint);
|
|
1515
|
+
url.searchParams.set("response_type", "code");
|
|
1516
|
+
url.searchParams.set("client_id", params.clientId);
|
|
1517
|
+
url.searchParams.set("redirect_uri", params.redirectURI);
|
|
1518
|
+
url.searchParams.set("state", params.state);
|
|
1519
|
+
if (params.scopes && params.scopes.length > 0) url.searchParams.set("scope", params.scopes.join(" "));
|
|
1520
|
+
if (params.prompt) url.searchParams.set("prompt", params.prompt);
|
|
1521
|
+
if (params.loginHint) url.searchParams.set("login_hint", params.loginHint);
|
|
1522
|
+
if (params.codeChallenge) {
|
|
1523
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
1524
|
+
url.searchParams.set("code_challenge_method", params.codeChallengeMethod ?? "S256");
|
|
1525
|
+
}
|
|
1526
|
+
if (params.extraParams) {
|
|
1527
|
+
for (const [key, value] of Object.entries(params.extraParams)) if (typeof value === "string" && value.length > 0) url.searchParams.set(key, value);
|
|
1528
|
+
}
|
|
1529
|
+
return url;
|
|
1530
|
+
};
|
|
1531
|
+
const parseScopes = (scopeValue) => {
|
|
1532
|
+
if (typeof scopeValue !== "string") return void 0;
|
|
1533
|
+
const scopes = scopeValue.split(/[,\s]+/).map((scope) => scope.trim()).filter(Boolean);
|
|
1534
|
+
return scopes.length > 0 ? scopes : void 0;
|
|
1535
|
+
};
|
|
1536
|
+
const readNumber = (value) => {
|
|
1537
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
1538
|
+
if (typeof value === "string") {
|
|
1539
|
+
const parsed = Number(value);
|
|
1540
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1541
|
+
}
|
|
1542
|
+
return void 0;
|
|
1543
|
+
};
|
|
1544
|
+
const normalizeOAuthTokens = (response) => {
|
|
1545
|
+
const accessToken = typeof response["access_token"] === "string" ? response["access_token"] : void 0;
|
|
1546
|
+
const refreshToken = typeof response["refresh_token"] === "string" ? response["refresh_token"] : void 0;
|
|
1547
|
+
const tokenType = typeof response["token_type"] === "string" ? response["token_type"] : void 0;
|
|
1548
|
+
const idToken = typeof response["id_token"] === "string" ? response["id_token"] : void 0;
|
|
1549
|
+
const expiresIn = readNumber(response["expires_in"]);
|
|
1550
|
+
const accessTokenExpiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1e3) : void 0;
|
|
1551
|
+
return {
|
|
1552
|
+
accessToken,
|
|
1553
|
+
refreshToken,
|
|
1554
|
+
tokenType,
|
|
1555
|
+
idToken,
|
|
1556
|
+
accessTokenExpiresAt,
|
|
1557
|
+
scopes: parseScopes(response["scope"]),
|
|
1558
|
+
raw: response
|
|
1559
|
+
};
|
|
1560
|
+
};
|
|
1561
|
+
const parseOAuthTokenResponse = async (response) => {
|
|
1562
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1563
|
+
const status = response.status;
|
|
1564
|
+
let data;
|
|
1565
|
+
try {
|
|
1566
|
+
if (contentType.includes("application/json")) data = await response.json();
|
|
1567
|
+
else {
|
|
1568
|
+
const text = await response.text();
|
|
1569
|
+
data = Object.fromEntries(new URLSearchParams(text));
|
|
1570
|
+
}
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
return {
|
|
1573
|
+
ok: false,
|
|
1574
|
+
status,
|
|
1575
|
+
error: err instanceof Error ? err.message : "Invalid token response"
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
if (!response.ok) {
|
|
1579
|
+
const message = typeof data.error_description === "string" ? data.error_description : typeof data.error === "string" ? data.error : `Token request failed with status ${status}`;
|
|
1580
|
+
return {
|
|
1581
|
+
ok: false,
|
|
1582
|
+
status,
|
|
1583
|
+
error: message,
|
|
1584
|
+
data
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
if (!data || typeof data !== "object") return {
|
|
1588
|
+
ok: false,
|
|
1589
|
+
status,
|
|
1590
|
+
error: "Token response was empty"
|
|
1591
|
+
};
|
|
1592
|
+
return {
|
|
1593
|
+
ok: true,
|
|
1594
|
+
data
|
|
1595
|
+
};
|
|
1596
|
+
};
|
|
1597
|
+
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region src/oauth/providers/github/github.ts
|
|
1600
|
+
const defaultScopes = ["read:user", "user:email"];
|
|
1601
|
+
const defaultUserAgent = "fragno-auth";
|
|
1602
|
+
const tokenEndpoint = "https://github.com/login/oauth/access_token";
|
|
1603
|
+
const profileEndpoint = "https://api.github.com/user";
|
|
1604
|
+
const emailsEndpoint = "https://api.github.com/user/emails";
|
|
1605
|
+
const createGithubOAuthClient = (options) => {
|
|
1606
|
+
const fetcher = options?.fetcher ?? fetch;
|
|
1607
|
+
const userAgent = options?.userAgent ?? defaultUserAgent;
|
|
1608
|
+
return {
|
|
1609
|
+
exchangeCode: async ({ code, redirectURI, clientId, clientSecret, codeVerifier }) => {
|
|
1610
|
+
const body = new URLSearchParams({
|
|
1611
|
+
client_id: clientId,
|
|
1612
|
+
client_secret: clientSecret,
|
|
1613
|
+
code,
|
|
1614
|
+
redirect_uri: redirectURI
|
|
1615
|
+
});
|
|
1616
|
+
if (codeVerifier) body.set("code_verifier", codeVerifier);
|
|
1617
|
+
const response = await fetcher(tokenEndpoint, {
|
|
1618
|
+
method: "POST",
|
|
1619
|
+
headers: {
|
|
1620
|
+
accept: "application/json",
|
|
1621
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
1622
|
+
},
|
|
1623
|
+
body
|
|
1624
|
+
});
|
|
1625
|
+
const parsed = await parseOAuthTokenResponse(response);
|
|
1626
|
+
if (!parsed.ok) return null;
|
|
1627
|
+
return normalizeOAuthTokens(parsed.data);
|
|
1628
|
+
},
|
|
1629
|
+
fetchProfile: async (accessToken) => {
|
|
1630
|
+
const response = await fetcher(profileEndpoint, { headers: {
|
|
1631
|
+
"User-Agent": userAgent,
|
|
1632
|
+
authorization: `Bearer ${accessToken}`
|
|
1633
|
+
} });
|
|
1634
|
+
if (!response.ok) return null;
|
|
1635
|
+
return await response.json();
|
|
1636
|
+
},
|
|
1637
|
+
fetchEmails: async (accessToken) => {
|
|
1638
|
+
const response = await fetcher(emailsEndpoint, { headers: {
|
|
1639
|
+
"User-Agent": userAgent,
|
|
1640
|
+
authorization: `Bearer ${accessToken}`
|
|
1641
|
+
} });
|
|
1642
|
+
if (!response.ok) return [];
|
|
1643
|
+
return await response.json();
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
};
|
|
1647
|
+
const github = (options) => {
|
|
1648
|
+
const client = options.client ?? createGithubOAuthClient();
|
|
1649
|
+
const authorizationEndpoint = options.authorizationEndpoint ?? "https://github.com/login/oauth/authorize";
|
|
1650
|
+
return {
|
|
1651
|
+
id: "github",
|
|
1652
|
+
name: "GitHub",
|
|
1653
|
+
options,
|
|
1654
|
+
createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
|
|
1655
|
+
const resolvedScopes = options.disableDefaultScope ? [] : [...defaultScopes];
|
|
1656
|
+
if (options.scope) resolvedScopes.push(...options.scope);
|
|
1657
|
+
if (scopes) resolvedScopes.push(...scopes);
|
|
1658
|
+
return createAuthorizationURL({
|
|
1659
|
+
authorizationEndpoint,
|
|
1660
|
+
clientId: options.clientId,
|
|
1661
|
+
redirectURI,
|
|
1662
|
+
state,
|
|
1663
|
+
scopes: resolvedScopes,
|
|
1664
|
+
prompt: options.prompt,
|
|
1665
|
+
loginHint: loginHint ?? void 0,
|
|
1666
|
+
extraParams: loginHint ? { login: loginHint } : void 0
|
|
1667
|
+
});
|
|
1668
|
+
},
|
|
1669
|
+
validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
|
|
1670
|
+
return client.exchangeCode({
|
|
1671
|
+
code,
|
|
1672
|
+
redirectURI,
|
|
1673
|
+
clientId: options.clientId,
|
|
1674
|
+
clientSecret: options.clientSecret,
|
|
1675
|
+
codeVerifier
|
|
1676
|
+
});
|
|
1677
|
+
},
|
|
1678
|
+
refreshAccessToken: options.refreshAccessToken,
|
|
1679
|
+
async getUserInfo(token) {
|
|
1680
|
+
if (options.getUserInfo) return options.getUserInfo(token);
|
|
1681
|
+
if (!token.accessToken) return null;
|
|
1682
|
+
const profile = await client.fetchProfile(token.accessToken);
|
|
1683
|
+
if (!profile) return null;
|
|
1684
|
+
const emails = await client.fetchEmails(token.accessToken);
|
|
1685
|
+
if (!profile.email && emails.length > 0) profile.email = (emails.find((entry) => entry.primary) ?? emails[0])?.email;
|
|
1686
|
+
const emailVerified = emails.find((entry) => entry.email === profile.email)?.verified ?? false;
|
|
1687
|
+
const mapped = await options.mapProfileToUser?.(profile);
|
|
1688
|
+
return {
|
|
1689
|
+
user: {
|
|
1690
|
+
id: profile.id,
|
|
1691
|
+
name: profile.name || profile.login,
|
|
1692
|
+
email: profile.email ?? null,
|
|
1693
|
+
image: profile.avatar_url,
|
|
1694
|
+
emailVerified,
|
|
1695
|
+
...mapped
|
|
1696
|
+
},
|
|
1697
|
+
data: profile
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1703
|
+
//#endregion
|
|
1704
|
+
//#region src/index.ts
|
|
1705
|
+
const authFragmentDefinition = defineFragment("auth").extend((x) => x).providesBaseService(() => {}).build();
|
|
1706
|
+
function createAuthFragment(config = {}, fragnoConfig) {
|
|
1707
|
+
return {};
|
|
1708
|
+
}
|
|
1709
|
+
function createAuthFragmentClients(fragnoConfig) {
|
|
1710
|
+
const config = { ...fragnoConfig };
|
|
1711
|
+
const b = createClientBuilder(authFragmentDefinition, config, [
|
|
1712
|
+
userActionsRoutesFactory,
|
|
1713
|
+
sessionRoutesFactory,
|
|
1714
|
+
userOverviewRoutesFactory,
|
|
1715
|
+
organizationRoutesFactory,
|
|
1716
|
+
oauthRoutesFactory
|
|
1717
|
+
], {
|
|
1718
|
+
type: "options",
|
|
1719
|
+
options: { credentials: "include" }
|
|
1720
|
+
});
|
|
1721
|
+
const useMe = b.createHook("/me");
|
|
1722
|
+
const useSignUp = b.createMutator("POST", "/sign-up");
|
|
1723
|
+
const useSignIn = b.createMutator("POST", "/sign-in");
|
|
1724
|
+
const useSignOut = b.createMutator("POST", "/sign-out", (invalidate) => {
|
|
1725
|
+
invalidate("GET", "/me", {});
|
|
1726
|
+
invalidate("GET", "/users", {});
|
|
1727
|
+
});
|
|
1728
|
+
const useUsers = b.createHook("/users");
|
|
1729
|
+
const useUpdateUserRole = b.createMutator("PATCH", "/users/:userId/role", (invalidate) => {
|
|
1730
|
+
invalidate("GET", "/users", {});
|
|
1731
|
+
invalidate("GET", "/me", {});
|
|
1732
|
+
});
|
|
1733
|
+
const useChangePassword = b.createMutator("POST", "/change-password");
|
|
1734
|
+
const useOrganizations = b.createHook("/organizations");
|
|
1735
|
+
const useOrganization = b.createHook("/organizations/:organizationId");
|
|
1736
|
+
const useCreateOrganization = b.createMutator("POST", "/organizations", (invalidate) => {
|
|
1737
|
+
invalidate("GET", "/organizations", {});
|
|
1738
|
+
invalidate("GET", "/organizations/active", {});
|
|
1739
|
+
invalidate("GET", "/me", {});
|
|
1740
|
+
});
|
|
1741
|
+
const useUpdateOrganization = b.createMutator("PATCH", "/organizations/:organizationId", (invalidate, params) => {
|
|
1742
|
+
const organizationId = params.pathParams.organizationId;
|
|
1743
|
+
if (organizationId) {
|
|
1744
|
+
invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
|
|
1745
|
+
invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
|
|
1746
|
+
invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
|
|
1747
|
+
}
|
|
1748
|
+
invalidate("GET", "/organizations", {});
|
|
1749
|
+
invalidate("GET", "/organizations/active", {});
|
|
1750
|
+
invalidate("GET", "/me", {});
|
|
1751
|
+
});
|
|
1752
|
+
const useDeleteOrganization = b.createMutator("DELETE", "/organizations/:organizationId", (invalidate, params) => {
|
|
1753
|
+
const organizationId = params.pathParams.organizationId;
|
|
1754
|
+
if (organizationId) {
|
|
1755
|
+
invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
|
|
1756
|
+
invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
|
|
1757
|
+
invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
|
|
1758
|
+
}
|
|
1759
|
+
invalidate("GET", "/organizations", {});
|
|
1760
|
+
invalidate("GET", "/organizations/active", {});
|
|
1761
|
+
invalidate("GET", "/me", {});
|
|
1762
|
+
});
|
|
1763
|
+
const useActiveOrganization = b.createHook("/organizations/active");
|
|
1764
|
+
const useSetActiveOrganization = b.createMutator("POST", "/organizations/active", (invalidate) => {
|
|
1765
|
+
invalidate("GET", "/organizations/active", {});
|
|
1766
|
+
invalidate("GET", "/me", {});
|
|
1767
|
+
});
|
|
1768
|
+
const useOrganizationMembers = b.createHook("/organizations/:organizationId/members");
|
|
1769
|
+
const useAddOrganizationMember = b.createMutator("POST", "/organizations/:organizationId/members", (invalidate, params) => {
|
|
1770
|
+
const organizationId = params.pathParams.organizationId;
|
|
1771
|
+
if (!organizationId) return;
|
|
1772
|
+
invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
|
|
1773
|
+
invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
|
|
1774
|
+
invalidate("GET", "/organizations", {});
|
|
1775
|
+
invalidate("GET", "/me", {});
|
|
1776
|
+
});
|
|
1777
|
+
const useUpdateOrganizationMemberRoles = b.createMutator("PATCH", "/organizations/:organizationId/members/:memberId", (invalidate, params) => {
|
|
1778
|
+
const organizationId = params.pathParams.organizationId;
|
|
1779
|
+
if (!organizationId) return;
|
|
1780
|
+
invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
|
|
1781
|
+
invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
|
|
1782
|
+
invalidate("GET", "/organizations", {});
|
|
1783
|
+
invalidate("GET", "/me", {});
|
|
1784
|
+
});
|
|
1785
|
+
const useRemoveOrganizationMember = b.createMutator("DELETE", "/organizations/:organizationId/members/:memberId", (invalidate, params) => {
|
|
1786
|
+
const organizationId = params.pathParams.organizationId;
|
|
1787
|
+
if (!organizationId) return;
|
|
1788
|
+
invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
|
|
1789
|
+
invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
|
|
1790
|
+
invalidate("GET", "/organizations", {});
|
|
1791
|
+
invalidate("GET", "/me", {});
|
|
1792
|
+
});
|
|
1793
|
+
const useOrganizationInvitations = b.createHook("/organizations/:organizationId/invitations");
|
|
1794
|
+
const useInviteOrganizationMember = b.createMutator("POST", "/organizations/:organizationId/invitations", (invalidate, params) => {
|
|
1795
|
+
const organizationId = params.pathParams.organizationId;
|
|
1796
|
+
if (!organizationId) return;
|
|
1797
|
+
invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
|
|
1798
|
+
});
|
|
1799
|
+
const useRespondOrganizationInvitation = b.createMutator("PATCH", "/organizations/invitations/:invitationId", (invalidate) => {
|
|
1800
|
+
invalidate("GET", "/organizations/invitations", {});
|
|
1801
|
+
invalidate("GET", "/organizations", {});
|
|
1802
|
+
invalidate("GET", "/organizations/active", {});
|
|
1803
|
+
invalidate("GET", "/me", {});
|
|
1804
|
+
});
|
|
1805
|
+
const useUserInvitations = b.createHook("/organizations/invitations");
|
|
1806
|
+
const useOAuthAuthorize = b.createHook("/oauth/:provider/authorize");
|
|
1807
|
+
const useOAuthCallback = b.createHook("/oauth/:provider/callback");
|
|
1808
|
+
const readRawMe = async (params) => {
|
|
1809
|
+
if (params?.sessionId) return useMe.query({ query: { sessionId: params.sessionId } });
|
|
1810
|
+
return useMe.query();
|
|
1811
|
+
};
|
|
1812
|
+
const defaultOrganizationPreference = createDefaultOrganizationPreferenceState({
|
|
1813
|
+
meStore: useMe.store(),
|
|
1814
|
+
readMe: readRawMe,
|
|
1815
|
+
getAccountId: (me) => me.user.id
|
|
1816
|
+
});
|
|
1817
|
+
return {
|
|
1818
|
+
useSignUp,
|
|
1819
|
+
useSignIn,
|
|
1820
|
+
useSignOut,
|
|
1821
|
+
useMe,
|
|
1822
|
+
useDefaultOrganizationPreference: b.createStore(defaultOrganizationPreference.store),
|
|
1823
|
+
useUsers,
|
|
1824
|
+
useUpdateUserRole,
|
|
1825
|
+
useChangePassword,
|
|
1826
|
+
useOrganizations,
|
|
1827
|
+
useOrganization,
|
|
1828
|
+
useCreateOrganization,
|
|
1829
|
+
useUpdateOrganization,
|
|
1830
|
+
useDeleteOrganization,
|
|
1831
|
+
useActiveOrganization,
|
|
1832
|
+
useSetActiveOrganization,
|
|
1833
|
+
useOrganizationMembers,
|
|
1834
|
+
useAddOrganizationMember,
|
|
1835
|
+
useUpdateOrganizationMemberRoles,
|
|
1836
|
+
useRemoveOrganizationMember,
|
|
1837
|
+
useOrganizationInvitations,
|
|
1838
|
+
useInviteOrganizationMember,
|
|
1839
|
+
useRespondOrganizationInvitation,
|
|
1840
|
+
useUserInvitations,
|
|
1841
|
+
useOAuthAuthorize,
|
|
1842
|
+
useOAuthCallback,
|
|
1843
|
+
signIn: { email: async ({ email, password, session, rememberMe: _rememberMe }) => {
|
|
1844
|
+
return useSignIn.mutateQuery({ body: {
|
|
1845
|
+
email,
|
|
1846
|
+
password,
|
|
1847
|
+
session
|
|
1848
|
+
} });
|
|
1849
|
+
} },
|
|
1850
|
+
signUp: { email: async ({ email, password }) => {
|
|
1851
|
+
return useSignUp.mutateQuery({ body: {
|
|
1852
|
+
email,
|
|
1853
|
+
password
|
|
1854
|
+
} });
|
|
1855
|
+
} },
|
|
1856
|
+
signOut: (params) => {
|
|
1857
|
+
return useSignOut.mutateQuery({ body: params?.sessionId ? { sessionId: params.sessionId } : {} });
|
|
1858
|
+
},
|
|
1859
|
+
me: defaultOrganizationPreference.me,
|
|
1860
|
+
defaultOrganization: defaultOrganizationPreference.defaultOrganization,
|
|
1861
|
+
oauth: {
|
|
1862
|
+
getAuthorizationUrl: async (params) => {
|
|
1863
|
+
return useOAuthAuthorize.query({
|
|
1864
|
+
path: { provider: params.provider },
|
|
1865
|
+
query: {
|
|
1866
|
+
redirectUri: params.redirectUri,
|
|
1867
|
+
returnTo: params.returnTo,
|
|
1868
|
+
link: params.link ? "true" : void 0,
|
|
1869
|
+
sessionId: params.sessionId,
|
|
1870
|
+
session: serializeSessionSeedForQuery(params.session),
|
|
1871
|
+
scope: params.scope,
|
|
1872
|
+
loginHint: params.loginHint
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
},
|
|
1876
|
+
callback: async (params) => {
|
|
1877
|
+
return useOAuthCallback.query({
|
|
1878
|
+
path: { provider: params.provider },
|
|
1879
|
+
query: {
|
|
1880
|
+
code: params.code,
|
|
1881
|
+
state: params.state,
|
|
1882
|
+
requestSignUp: params.requestSignUp ? "true" : void 0
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
//#endregion
|
|
1891
|
+
export { DEFAULT_ORGANIZATION_CHANGE_EVENT, DEFAULT_ORGANIZATION_STORAGE_KEY, NO_ORGANIZATIONS_ERROR_MESSAGE, authFragmentDefinition, clearDefaultOrganizationId, createAuthFragment, createAuthFragmentClients, createGithubOAuthClient, findOrganizationEntry, getDefaultOrganizationChangeEventName, getDefaultOrganizationStorageKey, github, readDefaultOrganizationId, resolveDefaultOrganization, setDefaultOrganizationForMe, subscribeToDefaultOrganizationPreference, syncDefaultOrganizationPreference, writeDefaultOrganizationId };
|
|
1892
|
+
//# sourceMappingURL=src-Ck4bl2NH.js.map
|