@maudecode/kanvil 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/server/dist/{auth-BscXb1NQ.mjs → auth-I1XEr0d4.mjs} +272 -7
- package/apps/server/dist/{config-Qxr_7oqZ.mjs → config-CbNqtJEj.mjs} +8 -2
- package/apps/server/dist/index.mjs +22 -186
- package/apps/server/dist/migrate.mjs +1 -1
- package/apps/server/dist/recover.mjs +21 -6
- package/apps/server/drizzle/0013_authentication_policy.sql +2 -0
- package/apps/server/drizzle/meta/0013_snapshot.json +4240 -0
- package/apps/server/drizzle/meta/_journal.json +7 -0
- package/apps/web/dist/assets/{CompositeItem-D9KvUNiq.js → CompositeItem-BWPJ5Mol.js} +1 -1
- package/apps/web/dist/assets/admin-settings-DZqkC6ju.js +1 -0
- package/apps/web/dist/assets/{date-picker-lhXxKIlN.js → date-picker-6H_iwP6t.js} +1 -1
- package/apps/web/dist/assets/{ellipsis-CN74O_EP.js → ellipsis-BT172Uvu.js} +1 -1
- package/apps/web/dist/assets/{forms-456UbfIC.js → forms-B4Vg3CPz.js} +23 -23
- package/apps/web/dist/assets/image-edit-trigger-DGmVtWLC.js +1 -0
- package/apps/web/dist/assets/index-C_a21taR.js +50 -0
- package/apps/web/dist/assets/index-DM-D_3Sv.css +2 -0
- package/apps/web/dist/assets/kbd-tTQGZxaK.js +1 -0
- package/apps/web/dist/assets/link-DoIP9KJ6.js +2 -0
- package/apps/web/dist/assets/{list-order-1UoOT5nR.js → list-order-Bzsrihyr.js} +1 -1
- package/apps/web/dist/assets/member-directory-BiCE8DRq.js +1 -0
- package/apps/web/dist/assets/popover-BhzZ0U1d.js +1 -0
- package/apps/web/dist/assets/project-label-provider-9L7Uts3_.js +1 -0
- package/apps/web/dist/assets/project-settings-Q7Q9Reec.js +1 -0
- package/apps/web/dist/assets/{rotate-ccw-BLeWpuu1.js → rotate-ccw-sjyLwLLy.js} +1 -1
- package/apps/web/dist/assets/settings-B6qD6q1J.js +1 -0
- package/apps/web/dist/assets/settings-layout-B-AwUIMu.js +1 -0
- package/apps/web/dist/assets/switch-D0nFHj6l.js +1 -0
- package/apps/web/dist/assets/{table-BQbZG4BK.js → table-CYZiwo9g.js} +1 -1
- package/apps/web/dist/assets/ticket-detail-B-3vbHSB.js +2 -0
- package/apps/web/dist/assets/ticket-editor-C3d8TZIT.js +1 -0
- package/apps/web/dist/assets/ticket-list-LtGGOnwN.js +5 -0
- package/apps/web/dist/assets/ticket-tags-2rdmkcJV.js +1 -0
- package/apps/web/dist/assets/{upload-5Wg5ou_Z.js → upload-DXPRzAaa.js} +1 -1
- package/apps/web/dist/assets/useNavigate-DFohfUD3.js +1 -0
- package/apps/web/dist/assets/workspace-settings-D3tCm1vH.js +1 -0
- package/apps/web/dist/index.html +8 -4
- package/docs/api.md +4 -0
- package/docs/mobile.md +0 -2
- package/docs/releasing.md +3 -1
- package/docs/self-hosting.md +9 -1
- package/package.json +2 -1
- package/apps/web/dist/assets/admin-settings-B0u2-aAK.js +0 -1
- package/apps/web/dist/assets/camera-Cv5wKkKn.js +0 -1
- package/apps/web/dist/assets/index-CVzq9BMC.css +0 -2
- package/apps/web/dist/assets/index-Dt8wYIVA.js +0 -51
- package/apps/web/dist/assets/instance-BCNvOvYa.js +0 -1
- package/apps/web/dist/assets/member-directory-C81ugis6.js +0 -1
- package/apps/web/dist/assets/popover-BWk65ByY.js +0 -1
- package/apps/web/dist/assets/project-settings-DnpVfVf5.js +0 -1
- package/apps/web/dist/assets/settings-DUXuOgWn.js +0 -1
- package/apps/web/dist/assets/settings-layout-xR4hrfte.js +0 -1
- package/apps/web/dist/assets/ticket-detail-B9M_eaVu.js +0 -2
- package/apps/web/dist/assets/ticket-editor-Chg1lDuU.js +0 -1
- package/apps/web/dist/assets/ticket-list-C8500tXj.js +0 -5
- package/apps/web/dist/assets/ticket-tags-cbwQ93Gt.js +0 -1
- package/apps/web/dist/assets/workspace-settings-Dte44Kkx.js +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { a as instanceUpdateSchema, c as s3SettingsSchema, f as account, g as user, h as session, i as brokerSettingsSchema, l as searchAuthentication, m as auth_schema_exports, r as authenticationPolicySchema, u as searchSettingsSchema } from "./config-CbNqtJEj.mjs";
|
|
2
2
|
import { and, asc, count, desc, eq, getTableColumns, gt, ilike, inArray, isNotNull, isNull, lt, ne, notInArray, or, sql } from "drizzle-orm";
|
|
3
3
|
import { alias, bigint, boolean, customType, index, integer, jsonb, pgTable, primaryKey, text, timestamp, unique, uuid } from "drizzle-orm/pg-core";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
5
|
+
import { decodeJwt } from "jose";
|
|
6
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from "node:crypto";
|
|
6
7
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
7
8
|
import { icons } from "@iconify-json/lucide";
|
|
9
|
+
import { APIError, createAuthMiddleware } from "better-auth/api";
|
|
8
10
|
import { betterAuth } from "better-auth";
|
|
9
11
|
import { admin, genericOAuth, jwt } from "better-auth/plugins";
|
|
10
12
|
import { drizzleAdapter } from "@better-auth/drizzle-adapter";
|
|
@@ -13,6 +15,57 @@ import { expo } from "@better-auth/expo";
|
|
|
13
15
|
import { mcp } from "@better-auth/mcp";
|
|
14
16
|
import { cimd } from "@better-auth/cimd";
|
|
15
17
|
import { fetchClientMetadataResource } from "@better-auth/cimd/node";
|
|
18
|
+
//#region src/adapters/oidc.ts
|
|
19
|
+
const identitySchema = z.object({
|
|
20
|
+
sub: z.string().min(1),
|
|
21
|
+
email: z.email(),
|
|
22
|
+
email_verified: z.literal(true),
|
|
23
|
+
name: z.string().nullish(),
|
|
24
|
+
picture: z.string().nullish()
|
|
25
|
+
});
|
|
26
|
+
const discoverySchema = z.object({
|
|
27
|
+
issuer: z.string(),
|
|
28
|
+
userinfo_endpoint: z.url({ protocol: /^https?$/ })
|
|
29
|
+
});
|
|
30
|
+
function createOIDCUserInfo(config) {
|
|
31
|
+
return async (tokens) => {
|
|
32
|
+
if (!tokens.idToken) return null;
|
|
33
|
+
const claims = decodeJwt(tokens.idToken);
|
|
34
|
+
let profile = identitySchema.safeParse(claims);
|
|
35
|
+
if (!profile.success) {
|
|
36
|
+
if (!config.OIDC_DISCOVERY_URL || !tokens.accessToken) return null;
|
|
37
|
+
const discovery = await fetch(config.OIDC_DISCOVERY_URL, { signal: AbortSignal.timeout(1e4) });
|
|
38
|
+
if (!discovery.ok) return null;
|
|
39
|
+
const metadata = discoverySchema.parse(await discovery.json());
|
|
40
|
+
if (metadata.issuer !== claims.iss) return null;
|
|
41
|
+
const response = await fetch(metadata.userinfo_endpoint, {
|
|
42
|
+
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
|
43
|
+
signal: AbortSignal.timeout(1e4)
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) return null;
|
|
46
|
+
profile = identitySchema.safeParse(await response.json());
|
|
47
|
+
}
|
|
48
|
+
if (!profile.success || profile.data.sub !== claims.sub) return null;
|
|
49
|
+
return {
|
|
50
|
+
...profile.data,
|
|
51
|
+
id: profile.data.sub,
|
|
52
|
+
emailVerified: true,
|
|
53
|
+
name: profile.data.name ?? "",
|
|
54
|
+
image: profile.data.picture ?? void 0
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function matchesOIDCBinding(binding, issuer, clientId) {
|
|
59
|
+
if (!binding.idToken || !issuer || !clientId) return false;
|
|
60
|
+
try {
|
|
61
|
+
const claims = decodeJwt(binding.idToken);
|
|
62
|
+
const audience = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
|
|
63
|
+
return claims.iss === issuer && claims.sub === binding.accountId && audience.includes(clientId);
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
16
69
|
//#region ../../packages/contracts/src/index.ts
|
|
17
70
|
const states = [
|
|
18
71
|
"backlog",
|
|
@@ -325,6 +378,7 @@ const eventSchema = z.object({
|
|
|
325
378
|
createdAt: timestamp$1
|
|
326
379
|
});
|
|
327
380
|
z.object({
|
|
381
|
+
authentication: authenticationPolicySchema.default(() => authenticationPolicySchema.parse({})),
|
|
328
382
|
name: z.string(),
|
|
329
383
|
description: z.string().default(""),
|
|
330
384
|
version: z.string(),
|
|
@@ -815,7 +869,9 @@ const instanceSettings = pgTable("instance_settings", {
|
|
|
815
869
|
description: text("description").notNull().default("Ticketing for people and agents"),
|
|
816
870
|
storageBackendId: uuid("storage_backend_id").references(() => storageBackends.id),
|
|
817
871
|
searchConfig: text("search_config"),
|
|
818
|
-
brokerConfig: text("broker_config")
|
|
872
|
+
brokerConfig: text("broker_config"),
|
|
873
|
+
emailPasswordEnabled: boolean("email_password_enabled").notNull().default(true),
|
|
874
|
+
ssoAutoProvision: boolean("sso_auto_provision").notNull().default(false)
|
|
819
875
|
});
|
|
820
876
|
const deliveryCheckpoints = pgTable("delivery_checkpoints", {
|
|
821
877
|
destination: text("destination").primaryKey(),
|
|
@@ -2593,15 +2649,204 @@ function createDomain(database, services = {}) {
|
|
|
2593
2649
|
};
|
|
2594
2650
|
}
|
|
2595
2651
|
//#endregion
|
|
2652
|
+
//#region src/instance.ts
|
|
2653
|
+
function storageIdentity(settings) {
|
|
2654
|
+
return createHash("sha256").update(JSON.stringify([
|
|
2655
|
+
settings.endpoint,
|
|
2656
|
+
settings.region,
|
|
2657
|
+
settings.bucket,
|
|
2658
|
+
settings.prefix,
|
|
2659
|
+
settings.forcePathStyle
|
|
2660
|
+
])).digest("hex");
|
|
2661
|
+
}
|
|
2662
|
+
function createInstance(database, config) {
|
|
2663
|
+
const key = createHash("sha256").update("kanvil:instance-config:v1:").update(config.BETTER_AUTH_SECRET).digest();
|
|
2664
|
+
function seal(value) {
|
|
2665
|
+
const iv = randomBytes(12), cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
2666
|
+
const bytes = Buffer.concat([cipher.update(JSON.stringify(value)), cipher.final()]);
|
|
2667
|
+
return Buffer.concat([
|
|
2668
|
+
iv,
|
|
2669
|
+
cipher.getAuthTag(),
|
|
2670
|
+
bytes
|
|
2671
|
+
]).toString("base64");
|
|
2672
|
+
}
|
|
2673
|
+
function unseal(value) {
|
|
2674
|
+
try {
|
|
2675
|
+
const bytes = Buffer.from(value, "base64"), decipher = createDecipheriv("aes-256-gcm", key, bytes.subarray(0, 12));
|
|
2676
|
+
decipher.setAuthTag(bytes.subarray(12, 28));
|
|
2677
|
+
return JSON.parse(Buffer.concat([decipher.update(bytes.subarray(28)), decipher.final()]).toString());
|
|
2678
|
+
} catch {
|
|
2679
|
+
return fail(503, "configuration_key_changed", "Saved integration credentials cannot be decrypted. Restore the instance secret from your backup.");
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
async function read(db = database.db) {
|
|
2683
|
+
await db.insert(instanceSettings).values({ id: 1 }).onConflictDoNothing();
|
|
2684
|
+
return one(await db.select().from(instanceSettings).where(eq(instanceSettings.id, 1)));
|
|
2685
|
+
}
|
|
2686
|
+
async function backend(id, db = database.db) {
|
|
2687
|
+
const row = one(await db.select().from(storageBackends).where(eq(storageBackends.id, id)));
|
|
2688
|
+
const settings = s3SettingsSchema.parse(unseal(row.config));
|
|
2689
|
+
return {
|
|
2690
|
+
...row,
|
|
2691
|
+
settings,
|
|
2692
|
+
spec: {
|
|
2693
|
+
provider: "s3",
|
|
2694
|
+
config: settings
|
|
2695
|
+
}
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
async function resolveS3(settings, db = database.db) {
|
|
2699
|
+
settings = s3SettingsSchema.strip().parse(settings);
|
|
2700
|
+
const identity = storageIdentity(settings);
|
|
2701
|
+
const previous = (await db.select().from(storageBackends).where(eq(storageBackends.identity, identity)))[0];
|
|
2702
|
+
const old = previous ? s3SettingsSchema.parse(unseal(previous.config)) : void 0;
|
|
2703
|
+
const { secretAccessKey: suppliedSecret, ...publicSettings } = settings, secret = suppliedSecret || (old?.accessKeyId === settings.accessKeyId ? old.secretAccessKey : void 0);
|
|
2704
|
+
if (settings.accessKeyId && !secret) fail(400, "missing_secret", "Provide the S3 secret access key");
|
|
2705
|
+
return {
|
|
2706
|
+
...publicSettings,
|
|
2707
|
+
...secret ? { secretAccessKey: secret } : {}
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
async function resolveSearch(settings, db = database.db) {
|
|
2711
|
+
settings = searchSettingsSchema.strip().parse(settings);
|
|
2712
|
+
const row = await read(db), old = row.searchConfig ? searchSettingsSchema.parse(unseal(row.searchConfig)) : null, same = old?.url === settings.url && old.index === settings.index;
|
|
2713
|
+
const { apiKey: suppliedKey, password: suppliedPassword, auth: requestedAuth, ...publicSettings } = settings, auth = requestedAuth ?? (suppliedKey ? "apiKey" : suppliedPassword && settings.username ? "basic" : same && old ? searchAuthentication(old) : searchAuthentication(settings)), apiKey = suppliedKey || (same ? old?.apiKey : void 0), password = suppliedPassword || (same && old?.username === settings.username ? old?.password : void 0);
|
|
2714
|
+
if (auth === "apiKey" && !apiKey) fail(400, "missing_secret", "Provide the search API key");
|
|
2715
|
+
if (auth === "basic" && (!settings.username || !password)) fail(400, "missing_secret", "Provide the search username and password");
|
|
2716
|
+
return {
|
|
2717
|
+
...publicSettings,
|
|
2718
|
+
auth,
|
|
2719
|
+
...auth === "apiKey" && apiKey ? { apiKey } : {},
|
|
2720
|
+
...auth === "basic" && password ? { password } : {}
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
async function saveBackend(settings, db = database.db) {
|
|
2724
|
+
const complete = await resolveS3(settings, db), identity = storageIdentity(settings);
|
|
2725
|
+
return one(await db.insert(storageBackends).values({
|
|
2726
|
+
identity,
|
|
2727
|
+
config: seal(complete)
|
|
2728
|
+
}).onConflictDoUpdate({
|
|
2729
|
+
target: storageBackends.identity,
|
|
2730
|
+
set: { config: seal(complete) }
|
|
2731
|
+
}).returning());
|
|
2732
|
+
}
|
|
2733
|
+
async function storage() {
|
|
2734
|
+
if (config.KANVIL_STORAGE_PROVIDER === "local") return null;
|
|
2735
|
+
if (config.KANVIL_STORAGE_PROVIDER === "s3") return saveBackend(s3SettingsSchema.parse({
|
|
2736
|
+
endpoint: config.S3_ENDPOINT ?? "",
|
|
2737
|
+
region: config.S3_REGION,
|
|
2738
|
+
bucket: config.S3_BUCKET,
|
|
2739
|
+
prefix: config.S3_PREFIX,
|
|
2740
|
+
forcePathStyle: config.S3_FORCE_PATH_STYLE,
|
|
2741
|
+
accessKeyId: config.S3_ACCESS_KEY_ID ?? "",
|
|
2742
|
+
secretAccessKey: config.S3_SECRET_ACCESS_KEY
|
|
2743
|
+
}));
|
|
2744
|
+
const row = await read();
|
|
2745
|
+
return row.storageBackendId ? backend(row.storageBackendId) : null;
|
|
2746
|
+
}
|
|
2747
|
+
async function search() {
|
|
2748
|
+
const row = await read();
|
|
2749
|
+
if (config.KANVIL_SEARCH_PROVIDER === "postgres") return null;
|
|
2750
|
+
if (config.KANVIL_SEARCH_PROVIDER === "elasticsearch") {
|
|
2751
|
+
const settings = searchSettingsSchema.parse({
|
|
2752
|
+
url: config.ELASTICSEARCH_URL,
|
|
2753
|
+
index: config.ELASTICSEARCH_INDEX ?? `kanvil-${row.identity}`,
|
|
2754
|
+
auth: config.ELASTICSEARCH_AUTH,
|
|
2755
|
+
apiKey: config.ELASTICSEARCH_API_KEY,
|
|
2756
|
+
username: config.ELASTICSEARCH_USERNAME,
|
|
2757
|
+
password: config.ELASTICSEARCH_PASSWORD
|
|
2758
|
+
});
|
|
2759
|
+
return {
|
|
2760
|
+
...settings,
|
|
2761
|
+
auth: searchAuthentication(settings)
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
const settings = row.searchConfig ? searchSettingsSchema.parse(unseal(row.searchConfig)) : null;
|
|
2765
|
+
return settings ? {
|
|
2766
|
+
...settings,
|
|
2767
|
+
auth: searchAuthentication(settings)
|
|
2768
|
+
} : null;
|
|
2769
|
+
}
|
|
2770
|
+
async function broker() {
|
|
2771
|
+
if (config.KANVIL_BROKER_PROVIDER === "postgres") return null;
|
|
2772
|
+
if (config.KANVIL_BROKER_PROVIDER === "rabbitmq") return brokerSettingsSchema.parse({
|
|
2773
|
+
url: config.RABBITMQ_URL,
|
|
2774
|
+
exchange: config.RABBITMQ_EXCHANGE
|
|
2775
|
+
});
|
|
2776
|
+
const row = await read();
|
|
2777
|
+
return row.brokerConfig ? brokerSettingsSchema.parse(unseal(row.brokerConfig)) : null;
|
|
2778
|
+
}
|
|
2779
|
+
async function resolveBroker(settings, db = database.db) {
|
|
2780
|
+
settings = brokerSettingsSchema.strip().parse(settings);
|
|
2781
|
+
const row = await read(db), old = row.brokerConfig ? brokerSettingsSchema.parse(unseal(row.brokerConfig)) : null;
|
|
2782
|
+
const url = settings.url || old?.url;
|
|
2783
|
+
if (!url) return fail(400, "missing_secret", "Provide the RabbitMQ connection URL");
|
|
2784
|
+
return {
|
|
2785
|
+
...settings,
|
|
2786
|
+
url
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
async function authentication() {
|
|
2790
|
+
return authenticationPolicySchema.strip().parse(await read());
|
|
2791
|
+
}
|
|
2792
|
+
async function update(value, actorId, oidcIssuer) {
|
|
2793
|
+
const input = instanceUpdateSchema.parse(value);
|
|
2794
|
+
return database.db.transaction(async (tx) => {
|
|
2795
|
+
await tx.execute(sql`SELECT pg_advisory_xact_lock(7361083)`);
|
|
2796
|
+
const current = await read(tx);
|
|
2797
|
+
assertVersion(current, input.expectedVersion, "instance settings", "saving");
|
|
2798
|
+
if (input.authentication && !input.authentication.emailPasswordEnabled) {
|
|
2799
|
+
if (!config.OIDC_DISCOVERY_URL || !config.OIDC_CLIENT_ID || !config.OIDC_CLIENT_SECRET) fail(409, "oidc_required", "Configure an OIDC provider before disabling password sign-in");
|
|
2800
|
+
if (!(actorId ? await tx.select({
|
|
2801
|
+
idToken: account.idToken,
|
|
2802
|
+
accountId: account.accountId
|
|
2803
|
+
}).from(account).where(and(eq(account.userId, actorId), eq(account.providerId, "oidc"))) : []).some((binding) => matchesOIDCBinding(binding, oidcIssuer, config.OIDC_CLIENT_ID))) fail(409, "oidc_binding_required", "Link your OIDC account in User settings using the configured provider before disabling password sign-in");
|
|
2804
|
+
}
|
|
2805
|
+
let storageId = current.storageBackendId, searchConfig = current.searchConfig, brokerConfig = current.brokerConfig;
|
|
2806
|
+
if (!config.KANVIL_STORAGE_PROVIDER) storageId = input.storage.provider === "s3" ? (await saveBackend(input.storage, tx)).id : null;
|
|
2807
|
+
if (!config.KANVIL_SEARCH_PROVIDER) {
|
|
2808
|
+
if (input.search.provider === "postgres") searchConfig = null;
|
|
2809
|
+
else searchConfig = seal(await resolveSearch(input.search, tx));
|
|
2810
|
+
}
|
|
2811
|
+
if (!config.KANVIL_BROKER_PROVIDER && input.broker) brokerConfig = input.broker.provider === "postgres" ? null : seal(await resolveBroker(input.broker, tx));
|
|
2812
|
+
await tx.update(instanceSettings).set({
|
|
2813
|
+
...input.authentication,
|
|
2814
|
+
name: input.name,
|
|
2815
|
+
description: input.description,
|
|
2816
|
+
storageBackendId: storageId,
|
|
2817
|
+
searchConfig,
|
|
2818
|
+
brokerConfig,
|
|
2819
|
+
version: current.version + 1
|
|
2820
|
+
}).where(eq(instanceSettings.id, 1));
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
return {
|
|
2824
|
+
read,
|
|
2825
|
+
authentication,
|
|
2826
|
+
storage,
|
|
2827
|
+
search,
|
|
2828
|
+
backend,
|
|
2829
|
+
saveBackend,
|
|
2830
|
+
resolveS3,
|
|
2831
|
+
resolveSearch,
|
|
2832
|
+
broker,
|
|
2833
|
+
resolveBroker,
|
|
2834
|
+
update,
|
|
2835
|
+
config
|
|
2836
|
+
};
|
|
2837
|
+
}
|
|
2838
|
+
//#endregion
|
|
2596
2839
|
//#region src/auth.ts
|
|
2597
2840
|
function createAuth(database, config) {
|
|
2841
|
+
const instance = createInstance(database, config);
|
|
2598
2842
|
return betterAuth({
|
|
2599
2843
|
appName: "Kanvil",
|
|
2600
2844
|
baseURL: config.KANVIL_URL,
|
|
2601
2845
|
secret: config.BETTER_AUTH_SECRET,
|
|
2602
2846
|
database: drizzleAdapter(database.db, {
|
|
2603
2847
|
provider: "pg",
|
|
2604
|
-
schema: auth_schema_exports
|
|
2848
|
+
schema: auth_schema_exports,
|
|
2849
|
+
transaction: true
|
|
2605
2850
|
}),
|
|
2606
2851
|
trustedOrigins: config.trustedOrigins,
|
|
2607
2852
|
emailAndPassword: {
|
|
@@ -2611,7 +2856,26 @@ function createAuth(database, config) {
|
|
|
2611
2856
|
},
|
|
2612
2857
|
account: { accountLinking: {
|
|
2613
2858
|
enabled: true,
|
|
2614
|
-
disableImplicitLinking: true
|
|
2859
|
+
disableImplicitLinking: true,
|
|
2860
|
+
allowDifferentEmails: true
|
|
2861
|
+
} },
|
|
2862
|
+
onAPIError: { errorURL: `${config.KANVIL_URL}/login` },
|
|
2863
|
+
hooks: { before: createAuthMiddleware(async (ctx) => {
|
|
2864
|
+
if (ctx.path === "/sign-in/email" && !(await instance.authentication()).emailPasswordEnabled) throw new APIError("FORBIDDEN", {
|
|
2865
|
+
code: "password_sign_in_disabled",
|
|
2866
|
+
message: "Use your identity provider to sign in"
|
|
2867
|
+
});
|
|
2868
|
+
}) },
|
|
2869
|
+
user: { validateUserInfo: async ({ user, source }) => {
|
|
2870
|
+
if (source.method !== "oauth") return void 0;
|
|
2871
|
+
if (source.oauth?.providerId !== "oidc" || !user.email || user.emailVerified !== true) return {
|
|
2872
|
+
error: "email_not_verified",
|
|
2873
|
+
errorDescription: "The identity provider must verify your email address"
|
|
2874
|
+
};
|
|
2875
|
+
if (source.action === "create-user" && !(await instance.authentication()).ssoAutoProvision) return {
|
|
2876
|
+
error: "signup_disabled",
|
|
2877
|
+
errorDescription: "Ask an administrator to create and link your account"
|
|
2878
|
+
};
|
|
2615
2879
|
} },
|
|
2616
2880
|
telemetry: { enabled: false },
|
|
2617
2881
|
plugins: [
|
|
@@ -2649,6 +2913,7 @@ function createAuth(database, config) {
|
|
|
2649
2913
|
providerId: "oidc",
|
|
2650
2914
|
discoveryUrl: config.OIDC_DISCOVERY_URL,
|
|
2651
2915
|
requireIdTokenVerification: true,
|
|
2916
|
+
getUserInfo: createOIDCUserInfo(config),
|
|
2652
2917
|
clientId: config.OIDC_CLIENT_ID,
|
|
2653
2918
|
clientSecret: config.OIDC_CLIENT_SECRET,
|
|
2654
2919
|
scopes: [
|
|
@@ -2657,10 +2922,10 @@ function createAuth(database, config) {
|
|
|
2657
2922
|
"email"
|
|
2658
2923
|
],
|
|
2659
2924
|
pkce: true,
|
|
2660
|
-
disableSignUp:
|
|
2925
|
+
disableSignUp: false
|
|
2661
2926
|
}] : [] })
|
|
2662
2927
|
]
|
|
2663
2928
|
});
|
|
2664
2929
|
}
|
|
2665
2930
|
//#endregion
|
|
2666
|
-
export {
|
|
2931
|
+
export { pushDevices as A, memberSchema as B, instanceSettings as C, profilePhotos as D, notifications as E, workspaces as F, pushDeviceInput as G, notificationPreferencesSchema as H, ApiError as I, matchesOIDCBinding as J, roles as K, errorSchema as L, subscriptions as M, tickets as N, projects as O, workspaceMembers as P, hasAdminRole as R, events as S, notificationPreferences as T, okSchema as U, notificationPreferencesInput as V, operations as W, attachmentContent as _, fail as a, deliveryCheckpoints as b, requireRow as c, createTicketIndex as d, createBlobStorage as f, createPollingJob as g, createObjectStore as h, createDomain as i, storageBackends as j, pushDeliveries as k, projectRoleSQL as l, imageResponse as m, createInstance as n, json as o, decodeImage as p, scopes as q, assertVersion as r, one as s, createAuth as t, createTicketSearch as u, attachments as v, members as w, eventClock as x, blobObjects as y, isOperation as z };
|
|
@@ -385,13 +385,18 @@ const brokerSettingsSchema = z.object({
|
|
|
385
385
|
exchange: z.string().min(1).max(200).regex(/^[a-zA-Z0-9_.-]+$/).refine((value) => !value.startsWith("amq."), "Use an application exchange").default("kanvil.events")
|
|
386
386
|
}).strict();
|
|
387
387
|
const eventDeliverySettingsSchema = z.discriminatedUnion("provider", [z.object({ provider: z.literal("postgres") }).strict(), brokerSettingsSchema.extend({ provider: z.literal("rabbitmq") })]);
|
|
388
|
+
const authenticationPolicySchema = z.object({
|
|
389
|
+
emailPasswordEnabled: z.boolean().default(true),
|
|
390
|
+
ssoAutoProvision: z.boolean().default(false)
|
|
391
|
+
}).strict();
|
|
388
392
|
const instanceUpdateSchema = z.object({
|
|
389
393
|
expectedVersion: z.number().int().positive(),
|
|
390
394
|
name: z.string().trim().min(1).max(100),
|
|
391
395
|
description: z.string().max(500),
|
|
392
396
|
storage: storageSettingsSchema,
|
|
393
397
|
search: externalSearchSettingsSchema,
|
|
394
|
-
broker: eventDeliverySettingsSchema.optional()
|
|
398
|
+
broker: eventDeliverySettingsSchema.optional(),
|
|
399
|
+
authentication: authenticationPolicySchema.optional()
|
|
395
400
|
}).strict();
|
|
396
401
|
z.object({
|
|
397
402
|
name: z.string(),
|
|
@@ -402,6 +407,7 @@ const adminInstanceSchema = z.object({
|
|
|
402
407
|
version: z.number().int(),
|
|
403
408
|
name: z.string(),
|
|
404
409
|
description: z.string(),
|
|
410
|
+
authentication: authenticationPolicySchema.default(() => authenticationPolicySchema.parse({})),
|
|
405
411
|
storage: z.object({
|
|
406
412
|
provider: z.enum(["local", "s3"]),
|
|
407
413
|
source: z.enum(["deployment", "admin"]),
|
|
@@ -572,4 +578,4 @@ function readConfig(env = process.env) {
|
|
|
572
578
|
};
|
|
573
579
|
}
|
|
574
580
|
//#endregion
|
|
575
|
-
export {
|
|
581
|
+
export { instanceUpdateSchema as a, s3SettingsSchema as c, createDatabase as d, account as f, user as g, session as h, brokerSettingsSchema as i, searchAuthentication as l, auth_schema_exports as m, adminInstanceSchema as n, photoInputSchema as o, apikey as p, authenticationPolicySchema as r, photoResultSchema as s, readConfig as t, searchSettingsSchema as u };
|