@dyrected/core 2.5.40 → 2.5.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-config-BLMX-9MN.d.cts +1045 -0
- package/dist/app-config-BLMX-9MN.d.ts +1045 -0
- package/dist/chunk-4C4L6SD2.js +1524 -0
- package/dist/chunk-FCXE5CVC.js +1528 -0
- package/dist/index-BKYugiYp.d.cts +1988 -0
- package/dist/index-BKYugiYp.d.ts +1988 -0
- package/dist/index-BSWCiAen.d.cts +1983 -0
- package/dist/index-BSWCiAen.d.ts +1983 -0
- package/dist/index-BfLDb3b0.d.cts +1467 -0
- package/dist/index-BfLDb3b0.d.ts +1467 -0
- package/dist/index-Bi4sX_eC.d.cts +1491 -0
- package/dist/index-Bi4sX_eC.d.ts +1491 -0
- package/dist/index-C9Vp1DCT.d.cts +1563 -0
- package/dist/index-C9Vp1DCT.d.ts +1563 -0
- package/dist/index-CFezd8p1.d.cts +1986 -0
- package/dist/index-CFezd8p1.d.ts +1986 -0
- package/dist/index-DR2z1rrf.d.cts +1503 -0
- package/dist/index-DR2z1rrf.d.ts +1503 -0
- package/dist/index-DfkPT4NR.d.cts +1584 -0
- package/dist/index-DfkPT4NR.d.ts +1584 -0
- package/dist/index-xB1lMg79.d.cts +1456 -0
- package/dist/index-xB1lMg79.d.ts +1456 -0
- package/dist/index.cjs +78 -5
- package/dist/index.d.cts +77 -3
- package/dist/index.d.ts +77 -3
- package/dist/index.js +6 -2
- package/dist/server.cjs +462 -10
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +395 -7
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -5,13 +5,15 @@ import {
|
|
|
5
5
|
executeFieldAfterRead,
|
|
6
6
|
executeFieldBeforeChange,
|
|
7
7
|
generateOpenApi,
|
|
8
|
+
getAdminAuthCollection,
|
|
9
|
+
getPublicAdminAuthConfig,
|
|
8
10
|
initializeWorkflowDocument,
|
|
9
11
|
materializeWorkflowDocument,
|
|
10
12
|
normalizeConfig,
|
|
11
13
|
runCollectionHooks,
|
|
12
14
|
saveWorkflowDraft,
|
|
13
15
|
transitionWorkflow
|
|
14
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-FCXE5CVC.js";
|
|
15
17
|
|
|
16
18
|
// src/app.ts
|
|
17
19
|
import { Hono } from "hono";
|
|
@@ -1616,13 +1618,389 @@ var AuthController = class {
|
|
|
1616
1618
|
}
|
|
1617
1619
|
};
|
|
1618
1620
|
|
|
1619
|
-
// src/controllers/
|
|
1620
|
-
import {
|
|
1621
|
+
// src/controllers/admin-auth.controller.ts
|
|
1622
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1621
1623
|
import { TextEncoder as TextEncoder2 } from "util";
|
|
1624
|
+
import {
|
|
1625
|
+
SignJWT as SignJWT2,
|
|
1626
|
+
createRemoteJWKSet,
|
|
1627
|
+
jwtVerify as jwtVerify2,
|
|
1628
|
+
decodeJwt as decodeJwt2
|
|
1629
|
+
} from "jose";
|
|
1630
|
+
var AdminAuthController = class {
|
|
1631
|
+
constructor(config) {
|
|
1632
|
+
this.config = config;
|
|
1633
|
+
}
|
|
1634
|
+
config;
|
|
1635
|
+
providers(c) {
|
|
1636
|
+
return c.json(getPublicAdminAuthConfig(this.config.adminAuth));
|
|
1637
|
+
}
|
|
1638
|
+
async start(c) {
|
|
1639
|
+
const requestConfig = await this.getRequestConfig(c);
|
|
1640
|
+
const provider = this.getProvider(requestConfig, c.req.param("provider"));
|
|
1641
|
+
if (!provider) {
|
|
1642
|
+
return c.json({ error: true, message: "Admin auth provider not found." }, 404);
|
|
1643
|
+
}
|
|
1644
|
+
const siteId = this.getSiteId(c);
|
|
1645
|
+
const returnTo = this.normalizeReturnTo(c.req.query("returnTo"), c);
|
|
1646
|
+
if (provider.type === "oidc") {
|
|
1647
|
+
const redirectUrl = await this.buildOidcStartUrl(
|
|
1648
|
+
provider,
|
|
1649
|
+
returnTo,
|
|
1650
|
+
siteId,
|
|
1651
|
+
new URL(c.req.url).origin
|
|
1652
|
+
);
|
|
1653
|
+
return c.redirect(redirectUrl, 302);
|
|
1654
|
+
}
|
|
1655
|
+
if (!provider.startUrl) {
|
|
1656
|
+
return c.json({ error: true, message: "This provider does not expose a start URL." }, 400);
|
|
1657
|
+
}
|
|
1658
|
+
const url = new URL(provider.startUrl);
|
|
1659
|
+
url.searchParams.set("returnTo", returnTo);
|
|
1660
|
+
if (siteId) url.searchParams.set("siteId", siteId);
|
|
1661
|
+
url.searchParams.set("provider", provider.id);
|
|
1662
|
+
return c.redirect(url.toString(), 302);
|
|
1663
|
+
}
|
|
1664
|
+
async callback(c) {
|
|
1665
|
+
const requestConfig = await this.getRequestConfig(c);
|
|
1666
|
+
const provider = this.getProvider(requestConfig, c.req.param("provider"));
|
|
1667
|
+
if (!provider) {
|
|
1668
|
+
return c.json({ error: true, message: "Admin auth provider not found." }, 404);
|
|
1669
|
+
}
|
|
1670
|
+
try {
|
|
1671
|
+
const exchange = await this.completeProviderAuth(requestConfig, provider, c);
|
|
1672
|
+
const redirectUrl = new URL(exchange.returnTo);
|
|
1673
|
+
redirectUrl.searchParams.set("dyrectedExternalToken", exchange.token);
|
|
1674
|
+
redirectUrl.searchParams.set("dyrectedAdminCollection", exchange.collectionSlug);
|
|
1675
|
+
return c.redirect(redirectUrl.toString(), 302);
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
const message = error instanceof Error ? error.message : "External authentication failed.";
|
|
1678
|
+
const fallback = this.normalizeReturnTo(c.req.query("returnTo"), c);
|
|
1679
|
+
const redirectUrl = new URL(fallback);
|
|
1680
|
+
redirectUrl.searchParams.set("dyrectedExternalError", message);
|
|
1681
|
+
return c.redirect(redirectUrl.toString(), 302);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
async exchange(c) {
|
|
1685
|
+
const requestConfig = await this.getRequestConfig(c);
|
|
1686
|
+
const provider = this.getProvider(requestConfig, c.req.param("provider"));
|
|
1687
|
+
if (!provider) {
|
|
1688
|
+
return c.json({ error: true, message: "Admin auth provider not found." }, 404);
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
const exchange = await this.completeProviderAuth(requestConfig, provider, c);
|
|
1692
|
+
return c.json({
|
|
1693
|
+
token: exchange.token,
|
|
1694
|
+
collectionSlug: exchange.collectionSlug,
|
|
1695
|
+
providerId: provider.id
|
|
1696
|
+
});
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
const message = error instanceof Error ? error.message : "External authentication failed.";
|
|
1699
|
+
const status = message === "Access denied for this site." ? 403 : 401;
|
|
1700
|
+
return c.json({ error: true, message }, status);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
async logout(c) {
|
|
1704
|
+
return c.json({ success: true, message: "Logged out. Discard your token." });
|
|
1705
|
+
}
|
|
1706
|
+
getProvider(config, id) {
|
|
1707
|
+
if (config.adminAuth?.mode !== "external") return void 0;
|
|
1708
|
+
return config.adminAuth.providers.find((provider) => provider.id === id);
|
|
1709
|
+
}
|
|
1710
|
+
async completeProviderAuth(requestConfig, provider, c) {
|
|
1711
|
+
const resolved = provider.type === "oidc" ? await this.resolveOidcIdentity(provider, c) : await this.resolveCustomIdentity(provider, c);
|
|
1712
|
+
const identity = resolved.identity;
|
|
1713
|
+
const siteId = this.getSiteId(c);
|
|
1714
|
+
const adminCollection = getAdminAuthCollection(requestConfig);
|
|
1715
|
+
if (!adminCollection?.auth) {
|
|
1716
|
+
throw new Error("Admin auth collection is not configured.");
|
|
1717
|
+
}
|
|
1718
|
+
const db = c.get("config").db;
|
|
1719
|
+
if (!db) throw new Error("Database not configured.");
|
|
1720
|
+
let user = await this.findUserByExternalIdentity(db, adminCollection.slug, provider.id, identity.sub) ?? (identity.email ? (await db.find({
|
|
1721
|
+
collection: adminCollection.slug,
|
|
1722
|
+
where: { email: identity.email },
|
|
1723
|
+
limit: 1
|
|
1724
|
+
})).docs[0] : null);
|
|
1725
|
+
const provisioningMode = requestConfig.adminAuth?.provisioningMode ?? "jit_plus_membership_management";
|
|
1726
|
+
const allowJitProvisioning = provisioningMode !== "preprovisioned_only" && provider.allowJitProvisioning !== false;
|
|
1727
|
+
if (!user && !allowJitProvisioning) {
|
|
1728
|
+
throw new Error("This account has not been provisioned for admin access.");
|
|
1729
|
+
}
|
|
1730
|
+
const access = await this.resolveAccess(requestConfig, identity, provider.id, siteId, c, user);
|
|
1731
|
+
if (!access.allowed) {
|
|
1732
|
+
throw new Error("Access denied for this site.");
|
|
1733
|
+
}
|
|
1734
|
+
if (!user && allowJitProvisioning) {
|
|
1735
|
+
user = await db.create({
|
|
1736
|
+
collection: adminCollection.slug,
|
|
1737
|
+
data: {
|
|
1738
|
+
...await this.buildUserData(identity, provider.id, access.roles, access.data),
|
|
1739
|
+
password: await hashPassword(randomBytes2(32).toString("hex"))
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
} else if (user) {
|
|
1743
|
+
user = await db.update({
|
|
1744
|
+
collection: adminCollection.slug,
|
|
1745
|
+
id: user.id,
|
|
1746
|
+
data: await this.buildUserData(identity, provider.id, access.roles, access.data)
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
if (!user?.email) {
|
|
1750
|
+
throw new Error("Authenticated admin user is missing an email address.");
|
|
1751
|
+
}
|
|
1752
|
+
const token = await signCollectionToken({
|
|
1753
|
+
sub: user.id,
|
|
1754
|
+
email: user.email,
|
|
1755
|
+
collection: adminCollection.slug,
|
|
1756
|
+
providerId: provider.id,
|
|
1757
|
+
authSource: "external"
|
|
1758
|
+
});
|
|
1759
|
+
return {
|
|
1760
|
+
token,
|
|
1761
|
+
collectionSlug: adminCollection.slug,
|
|
1762
|
+
returnTo: resolved.returnTo
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
async resolveAccess(requestConfig, identity, providerId, siteId, c, user) {
|
|
1766
|
+
const resolved = await requestConfig.adminAuth?.resolveAccess?.({
|
|
1767
|
+
identity,
|
|
1768
|
+
providerId,
|
|
1769
|
+
siteId,
|
|
1770
|
+
workspaceId: c.get("workspaceId"),
|
|
1771
|
+
req: {
|
|
1772
|
+
method: c.req.method,
|
|
1773
|
+
path: c.req.path,
|
|
1774
|
+
url: c.req.url,
|
|
1775
|
+
headers: Object.fromEntries(c.req.raw.headers.entries())
|
|
1776
|
+
},
|
|
1777
|
+
user
|
|
1778
|
+
});
|
|
1779
|
+
if (resolved) return resolved;
|
|
1780
|
+
return { allowed: true, roles: identity.roles, data: {} };
|
|
1781
|
+
}
|
|
1782
|
+
async buildUserData(identity, providerId, roles, extraData) {
|
|
1783
|
+
return {
|
|
1784
|
+
...identity.email ? { email: identity.email } : {},
|
|
1785
|
+
...identity.name ? { name: identity.name } : {},
|
|
1786
|
+
...roles ? { roles } : {},
|
|
1787
|
+
...extraData ?? {},
|
|
1788
|
+
authProvider: providerId,
|
|
1789
|
+
externalSubject: identity.sub,
|
|
1790
|
+
authSource: "external",
|
|
1791
|
+
lastLoginAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
async findUserByExternalIdentity(db, collection, providerId, externalSubject) {
|
|
1795
|
+
const result = await db.find({
|
|
1796
|
+
collection,
|
|
1797
|
+
where: {
|
|
1798
|
+
authProvider: providerId,
|
|
1799
|
+
externalSubject
|
|
1800
|
+
},
|
|
1801
|
+
limit: 1
|
|
1802
|
+
});
|
|
1803
|
+
return result.docs[0] ?? null;
|
|
1804
|
+
}
|
|
1805
|
+
getSiteId(c) {
|
|
1806
|
+
return c.req.header("X-Site-Id") || c.get("siteId");
|
|
1807
|
+
}
|
|
1808
|
+
async getRequestConfig(c) {
|
|
1809
|
+
const siteId = this.getSiteId(c);
|
|
1810
|
+
if (!siteId || !this.config.onSchemaFetch) return this.config;
|
|
1811
|
+
const dynamic = await this.config.onSchemaFetch(siteId);
|
|
1812
|
+
return {
|
|
1813
|
+
...this.config,
|
|
1814
|
+
...dynamic.admin ? { admin: { ...this.config.admin, ...dynamic.admin } } : {},
|
|
1815
|
+
...dynamic.adminAuth ? { adminAuth: { ...this.config.adminAuth, ...dynamic.adminAuth } } : {},
|
|
1816
|
+
collections: dynamic.collections ? [...this.config.collections, ...dynamic.collections] : this.config.collections,
|
|
1817
|
+
globals: dynamic.globals ? [...this.config.globals, ...dynamic.globals] : this.config.globals
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
async buildOidcStartUrl(provider, returnTo, siteId, origin) {
|
|
1821
|
+
const discovery = await this.getOidcDiscovery(provider);
|
|
1822
|
+
const nonce = randomBytes2(16).toString("hex");
|
|
1823
|
+
const state = await this.signState({
|
|
1824
|
+
providerId: provider.id,
|
|
1825
|
+
returnTo,
|
|
1826
|
+
siteId,
|
|
1827
|
+
nonce
|
|
1828
|
+
});
|
|
1829
|
+
const url = new URL(provider.authorizationEndpoint || discovery.authorization_endpoint);
|
|
1830
|
+
url.searchParams.set("response_type", "code");
|
|
1831
|
+
url.searchParams.set("client_id", provider.clientId);
|
|
1832
|
+
url.searchParams.set("redirect_uri", provider.redirectUri || this.defaultCallbackPath(provider.id, origin));
|
|
1833
|
+
url.searchParams.set("scope", (provider.scopes ?? ["openid", "profile", "email"]).join(" "));
|
|
1834
|
+
url.searchParams.set("state", state);
|
|
1835
|
+
url.searchParams.set("nonce", nonce);
|
|
1836
|
+
return url.toString();
|
|
1837
|
+
}
|
|
1838
|
+
async resolveOidcIdentity(provider, c) {
|
|
1839
|
+
const code = c.req.query("code");
|
|
1840
|
+
const state = c.req.query("state");
|
|
1841
|
+
if (!code || !state) throw new Error("Missing OIDC callback parameters.");
|
|
1842
|
+
const parsedState = await this.verifyState(state, provider.id);
|
|
1843
|
+
const discovery = await this.getOidcDiscovery(provider);
|
|
1844
|
+
const redirectUri = provider.redirectUri || this.defaultCallbackPath(provider.id, new URL(c.req.url).origin);
|
|
1845
|
+
const tokenResponse = await fetch(provider.tokenEndpoint || discovery.token_endpoint, {
|
|
1846
|
+
method: "POST",
|
|
1847
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1848
|
+
body: new URLSearchParams({
|
|
1849
|
+
grant_type: "authorization_code",
|
|
1850
|
+
code,
|
|
1851
|
+
redirect_uri: redirectUri,
|
|
1852
|
+
client_id: provider.clientId,
|
|
1853
|
+
client_secret: provider.clientSecret
|
|
1854
|
+
})
|
|
1855
|
+
});
|
|
1856
|
+
if (!tokenResponse.ok) {
|
|
1857
|
+
throw new Error("OIDC token exchange failed.");
|
|
1858
|
+
}
|
|
1859
|
+
const tokenBody = await tokenResponse.json();
|
|
1860
|
+
const idToken = tokenBody.id_token;
|
|
1861
|
+
let claims = {};
|
|
1862
|
+
if (idToken) {
|
|
1863
|
+
const jwks = createRemoteJWKSet(new URL(discovery.jwks_uri));
|
|
1864
|
+
const { payload } = await jwtVerify2(idToken, jwks, {
|
|
1865
|
+
issuer: provider.issuer,
|
|
1866
|
+
audience: provider.clientId
|
|
1867
|
+
});
|
|
1868
|
+
if (parsedState.nonce && payload.nonce !== parsedState.nonce) {
|
|
1869
|
+
throw new Error("OIDC nonce verification failed.");
|
|
1870
|
+
}
|
|
1871
|
+
claims = payload;
|
|
1872
|
+
} else if (tokenBody.access_token && (provider.userInfoEndpoint || discovery.userinfo_endpoint)) {
|
|
1873
|
+
const userInfo = await fetch(provider.userInfoEndpoint || discovery.userinfo_endpoint, {
|
|
1874
|
+
headers: { Authorization: `Bearer ${tokenBody.access_token}` }
|
|
1875
|
+
});
|
|
1876
|
+
if (!userInfo.ok) throw new Error("OIDC userinfo request failed.");
|
|
1877
|
+
claims = await userInfo.json();
|
|
1878
|
+
} else {
|
|
1879
|
+
throw new Error("OIDC provider did not return usable identity claims.");
|
|
1880
|
+
}
|
|
1881
|
+
return {
|
|
1882
|
+
identity: this.mapClaims(claims, provider.claimMapping),
|
|
1883
|
+
returnTo: parsedState.returnTo
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
async resolveCustomIdentity(provider, c) {
|
|
1887
|
+
const body = c.req.method === "POST" ? await c.req.json().catch(() => ({})) : {};
|
|
1888
|
+
const tokenParam = provider.tokenParam || "token";
|
|
1889
|
+
const token = body?.[tokenParam] || c.req.query(tokenParam);
|
|
1890
|
+
if (!token) {
|
|
1891
|
+
throw new Error(`Missing ${tokenParam} for external auth exchange.`);
|
|
1892
|
+
}
|
|
1893
|
+
let claims;
|
|
1894
|
+
if (provider.secret) {
|
|
1895
|
+
const secret = new TextEncoder2().encode(provider.secret);
|
|
1896
|
+
const verified = await jwtVerify2(token, secret, {
|
|
1897
|
+
issuer: provider.issuer,
|
|
1898
|
+
audience: provider.audience
|
|
1899
|
+
});
|
|
1900
|
+
claims = verified.payload;
|
|
1901
|
+
} else if (provider.jwksUri) {
|
|
1902
|
+
const jwks = createRemoteJWKSet(new URL(provider.jwksUri));
|
|
1903
|
+
const verified = await jwtVerify2(token, jwks, {
|
|
1904
|
+
issuer: provider.issuer,
|
|
1905
|
+
audience: provider.audience
|
|
1906
|
+
});
|
|
1907
|
+
claims = verified.payload;
|
|
1908
|
+
} else {
|
|
1909
|
+
claims = decodeJwt2(token);
|
|
1910
|
+
if (provider.issuer && claims.iss !== provider.issuer) {
|
|
1911
|
+
throw new Error("External auth issuer mismatch.");
|
|
1912
|
+
}
|
|
1913
|
+
if (provider.audience) {
|
|
1914
|
+
const aud = claims.aud;
|
|
1915
|
+
const matches = Array.isArray(aud) ? aud.includes(provider.audience) : aud === provider.audience;
|
|
1916
|
+
if (!matches) throw new Error("External auth audience mismatch.");
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return {
|
|
1920
|
+
identity: this.mapClaims(claims, provider.claimMapping),
|
|
1921
|
+
returnTo: this.normalizeReturnTo(
|
|
1922
|
+
body?.returnTo || c.req.query("returnTo"),
|
|
1923
|
+
c
|
|
1924
|
+
)
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
mapClaims(claims, mapping) {
|
|
1928
|
+
const subKey = mapping?.sub || "sub";
|
|
1929
|
+
const emailKey = mapping?.email || "email";
|
|
1930
|
+
const nameKey = mapping?.name || "name";
|
|
1931
|
+
const rolesKey = mapping?.roles;
|
|
1932
|
+
const groupsKey = mapping?.groups;
|
|
1933
|
+
const siteIdsKey = mapping?.siteIds;
|
|
1934
|
+
const workspaceIdsKey = mapping?.workspaceIds;
|
|
1935
|
+
const sub = claims[subKey];
|
|
1936
|
+
if (typeof sub !== "string" || !sub) {
|
|
1937
|
+
throw new Error("External identity is missing a subject.");
|
|
1938
|
+
}
|
|
1939
|
+
return {
|
|
1940
|
+
sub,
|
|
1941
|
+
email: this.readStringClaim(claims[emailKey]),
|
|
1942
|
+
name: this.readStringClaim(claims[nameKey]),
|
|
1943
|
+
roles: this.readStringArrayClaim(rolesKey ? claims[rolesKey] : void 0),
|
|
1944
|
+
groups: this.readStringArrayClaim(groupsKey ? claims[groupsKey] : void 0),
|
|
1945
|
+
siteIds: this.readStringArrayClaim(siteIdsKey ? claims[siteIdsKey] : void 0),
|
|
1946
|
+
workspaceIds: this.readStringArrayClaim(workspaceIdsKey ? claims[workspaceIdsKey] : void 0),
|
|
1947
|
+
rawClaims: claims
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
readStringClaim(value) {
|
|
1951
|
+
return typeof value === "string" ? value : void 0;
|
|
1952
|
+
}
|
|
1953
|
+
readStringArrayClaim(value) {
|
|
1954
|
+
if (!value) return void 0;
|
|
1955
|
+
if (Array.isArray(value)) {
|
|
1956
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1957
|
+
}
|
|
1958
|
+
if (typeof value === "string") return [value];
|
|
1959
|
+
return void 0;
|
|
1960
|
+
}
|
|
1961
|
+
async getOidcDiscovery(provider) {
|
|
1962
|
+
const discoveryUrl = new URL(
|
|
1963
|
+
provider.issuer.replace(/\/$/, "") + "/.well-known/openid-configuration"
|
|
1964
|
+
);
|
|
1965
|
+
const response = await fetch(discoveryUrl.toString());
|
|
1966
|
+
if (!response.ok) throw new Error("Failed to load OIDC discovery document.");
|
|
1967
|
+
return response.json();
|
|
1968
|
+
}
|
|
1969
|
+
defaultCallbackPath(providerId, origin = "") {
|
|
1970
|
+
return `${origin}/api/admin/auth/${providerId}/callback`;
|
|
1971
|
+
}
|
|
1972
|
+
async signState(payload) {
|
|
1973
|
+
return new SignJWT2(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime("15m").sign(this.getStateSecret());
|
|
1974
|
+
}
|
|
1975
|
+
async verifyState(token, providerId) {
|
|
1976
|
+
const { payload } = await jwtVerify2(token, this.getStateSecret());
|
|
1977
|
+
const state = payload;
|
|
1978
|
+
if (state.providerId !== providerId) {
|
|
1979
|
+
throw new Error("OIDC state verification failed.");
|
|
1980
|
+
}
|
|
1981
|
+
return state;
|
|
1982
|
+
}
|
|
1983
|
+
getStateSecret() {
|
|
1984
|
+
const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET;
|
|
1985
|
+
if (!secret) {
|
|
1986
|
+
throw new Error("[dyrected/core] DYRECTED_JWT_SECRET is not set.");
|
|
1987
|
+
}
|
|
1988
|
+
return new TextEncoder2().encode(secret);
|
|
1989
|
+
}
|
|
1990
|
+
normalizeReturnTo(returnTo, c) {
|
|
1991
|
+
if (returnTo) return returnTo;
|
|
1992
|
+
const url = new URL(c.req.url);
|
|
1993
|
+
return `${url.origin}/admin`;
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1997
|
+
// src/controllers/preview.controller.ts
|
|
1998
|
+
import { SignJWT as SignJWT3, jwtVerify as jwtVerify3 } from "jose";
|
|
1999
|
+
import { TextEncoder as TextEncoder3 } from "util";
|
|
1622
2000
|
var PreviewController = class {
|
|
1623
2001
|
getSecret() {
|
|
1624
2002
|
const secret = process.env.DYRECTED_JWT_SECRET || process.env.JWT_SECRET || "dyrected-preview-secret-change-me";
|
|
1625
|
-
return new
|
|
2003
|
+
return new TextEncoder3().encode(secret);
|
|
1626
2004
|
}
|
|
1627
2005
|
/**
|
|
1628
2006
|
* POST /api/preview-token
|
|
@@ -1633,7 +2011,7 @@ var PreviewController = class {
|
|
|
1633
2011
|
if (!body?.collectionSlug || !body?.data) {
|
|
1634
2012
|
return c.json({ error: true, message: "collectionSlug and data are required." }, 400);
|
|
1635
2013
|
}
|
|
1636
|
-
const token = await new
|
|
2014
|
+
const token = await new SignJWT3({
|
|
1637
2015
|
collectionSlug: body.collectionSlug,
|
|
1638
2016
|
documentId: body.documentId,
|
|
1639
2017
|
data: body.data
|
|
@@ -1651,7 +2029,7 @@ var PreviewController = class {
|
|
|
1651
2029
|
return c.json({ error: true, message: "token query parameter is required." }, 400);
|
|
1652
2030
|
}
|
|
1653
2031
|
try {
|
|
1654
|
-
const { payload } = await
|
|
2032
|
+
const { payload } = await jwtVerify3(token, this.getSecret());
|
|
1655
2033
|
return c.json(payload);
|
|
1656
2034
|
} catch (err) {
|
|
1657
2035
|
return c.json({ error: true, message: "Invalid or expired preview token." }, 401);
|
|
@@ -1811,6 +2189,9 @@ function registerRoutes(app, config) {
|
|
|
1811
2189
|
if (dynamic.admin) {
|
|
1812
2190
|
config.admin = { ...config.admin, ...dynamic.admin };
|
|
1813
2191
|
}
|
|
2192
|
+
if (dynamic.adminAuth) {
|
|
2193
|
+
config.adminAuth = { ...config.adminAuth, ...dynamic.adminAuth };
|
|
2194
|
+
}
|
|
1814
2195
|
}
|
|
1815
2196
|
const user = c.get("user");
|
|
1816
2197
|
const accessArgs = { user, req: c.req, doc: null };
|
|
@@ -1899,7 +2280,8 @@ function registerRoutes(app, config) {
|
|
|
1899
2280
|
return c.json({
|
|
1900
2281
|
collections: filteredCollections,
|
|
1901
2282
|
globals: filteredGlobals,
|
|
1902
|
-
admin: config.admin || {}
|
|
2283
|
+
admin: config.admin || {},
|
|
2284
|
+
adminAuth: getPublicAdminAuthConfig(config.adminAuth)
|
|
1903
2285
|
});
|
|
1904
2286
|
});
|
|
1905
2287
|
app.get("/api/dyrected/options/:collection/:field", optionalAuth(), async (c) => {
|
|
@@ -2091,6 +2473,12 @@ function registerRoutes(app, config) {
|
|
|
2091
2473
|
app.delete(`${prefix}/media/:id`, accessGate(col, "delete"), (c) => mediaController.delete(c));
|
|
2092
2474
|
}
|
|
2093
2475
|
}
|
|
2476
|
+
const adminAuthController = new AdminAuthController(config);
|
|
2477
|
+
app.get("/api/admin/auth/providers", (c) => adminAuthController.providers(c));
|
|
2478
|
+
app.get("/api/admin/auth/:provider/start", (c) => adminAuthController.start(c));
|
|
2479
|
+
app.get("/api/admin/auth/:provider/callback", (c) => adminAuthController.callback(c));
|
|
2480
|
+
app.post("/api/admin/auth/:provider/exchange", (c) => adminAuthController.exchange(c));
|
|
2481
|
+
app.post("/api/admin/logout", (c) => adminAuthController.logout(c));
|
|
2094
2482
|
for (const collection of config.collections) {
|
|
2095
2483
|
if (!collection.auth) continue;
|
|
2096
2484
|
const path = `/api/collections/${collection.slug}`;
|