Skip to content

Commit 0d627e6

Browse files
committed
Release v0.2.8
1 parent 690c0af commit 0d627e6

89 files changed

Lines changed: 5418 additions & 288 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎RELEASE.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2-
"version": "0.2.7",
2+
"version": "0.2.8",
33
"license": "FSL-1.1-ALv2",
44
"baselineSha256": "7847f65a9b20b366a60d52d19ca3cd6bdd1bb09b8cb506659a270f27a21d8877",
55
"e2b": {
66
"recipeSha256": "761809ee2efa2b6c750451ace29283cbb8e55b6ce3b5b14c2bb4509df71df801",
77
"profileSha256": "414dbecee2d47de36016ea05c736e2edff3a0deb298babf923887ea184a01c10"
88
},
99
"dxd": {
10-
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.7/dxd-linux-x64",
11-
"sha256": "2e3408e65cad33ccea13ba9f95321693cb22beb7944d5e02438184921190c700"
10+
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.8/dxd-linux-x64",
11+
"sha256": "b793f9d2c7032e5fe3e233e40fc906b12648c3dd53969b5b7ef44d2d075d219b"
1212
},
1313
"toolchain": {
1414
"node": ">=22.19.0",

‎apps/core/migration-manifest.json‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"previousAcceptedD1Migration": "0000_baseline_v0.1.0.sql",
55
"acceptedDestructiveD1Migrations": [
66
"0002_release_v0.1.2.sql",
7-
"0003_release_v0.2.0.sql"
7+
"0003_release_v0.2.0.sql",
8+
"0005_release_v0.2.8.sql"
89
],
910
"d1": [
1011
{
@@ -31,6 +32,11 @@
3132
"filename": "0004_release_v0.2.7.sql",
3233
"sha256": "8be098b80c880ff8795e4d83da23eaac8e8d622d4c99b074aa4447a3cae1d98b",
3334
"classification": "non-destructive"
35+
},
36+
{
37+
"filename": "0005_release_v0.2.8.sql",
38+
"sha256": "d96a76cbee9e18c273ba6da1a57bf0ab62e77e23d4b05420e6eb8ef7cbe501d8",
39+
"classification": "destructive"
3440
}
3541
],
3642
"durableObjects": [

‎apps/core/migrations/0005_release_v0.2.8.sql‎

Lines changed: 783 additions & 0 deletions
Large diffs are not rendered by default.

‎apps/core/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@dx/core",
33
"private": true,
4-
"version": "0.2.7",
4+
"version": "0.2.8",
55
"license": "FSL-1.1-ALv2",
66
"type": "module",
77
"scripts": {

‎apps/core/src/app.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import { submissionStartupObservation } from "./observability/submission-startup
1919
import { projectRoutes } from "./projects/routes.js";
2020
import { readinessHandler } from "./readiness/handler.js";
2121
import { realtimeRoutes } from "./realtime/realtime-hub.js";
22+
import {
23+
inviteAcceptRoutes,
24+
publicInviteRoutes,
25+
} from "./settings/members/routes.js";
2226
import { settingsRoutes } from "./settings/routes.js";
2327
import { pluginTriggerIngressRoutes } from "./settings/triggers/routes.js";
2428
import { scheduleUsageRetention } from "./settings/usage/retention.js";
@@ -49,12 +53,14 @@ app.get("/readyz", readinessHandler);
4953
app.route("/api/auth", authRoutes);
5054
app.route("/api/workload-identity", workloadIdentityRoutes);
5155
app.route("/api/triggers", pluginTriggerIngressRoutes);
56+
app.route("/api/invites", publicInviteRoutes);
5257
app.route("/api/source/bitbucket/git", bitbucketGitGatewayRoutes);
5358
app.route("/v1/integrations/github", githubWebhookRoutes);
5459
app.route("/v1", threadDaemonRoutes);
5560
app.use("/v1/*", authenticate);
5661
app.use("/v1/*", authorizeApiTokenScope);
5762
app.route("/v1/dictation", dictationRoutes);
63+
app.route("/v1/invites", inviteAcceptRoutes);
5864
app.route("/v1/integrations/bitbucket", bitbucketControlPlaneRoutes);
5965
app.route("/v1/integrations/github", githubControlPlaneRoutes);
6066
app.route("/v1/projects", projectRoutes);

‎apps/core/src/auth/better-auth.ts‎

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { APIError } from "better-auth/api";
1414
import { organization } from "better-auth/plugins";
1515
import { Effect, Option, Schema } from "effect";
1616
import type { Bindings } from "../http/types.js";
17+
import { inviteSignupAdmission } from "../settings/members/service.js";
1718
import { cloudflareMagicLink } from "./cloudflare-magic-link.js";
1819
import { emailPassword } from "./email-password.js";
1920
import type { AuthenticationRequirements } from "./requirements.js";
@@ -53,12 +54,19 @@ const defaultAuthenticationPlugins = (
5354
if (browserAuthenticationMode(requirements, bindings) === "email-password") {
5455
return [
5556
requirements.environment === "selfhost"
56-
? emailPassword(undefined, selfhostSignupEnabled(bindings))
57+
? emailPassword(
58+
undefined,
59+
selfhostSignupEnabled(bindings),
60+
bindings.DB === undefined
61+
? undefined
62+
: (callbackURL) =>
63+
inviteSignupAdmission(bindings.DB, callbackURL),
64+
)
5765
: emailPassword(undefined, true),
5866
];
5967
}
60-
if (bindings.DB === undefined)
61-
throw new Error("D1 database binding is required.");
68+
const db = bindings.DB;
69+
if (db === undefined) throw new Error("D1 database binding is required.");
6270
if (bindings.EMAIL === undefined)
6371
throw new Error("Cloudflare email binding is required for deployed auth.");
6472
const from = bindings.DX_AUTH_EMAIL_FROM?.trim();
@@ -67,7 +75,9 @@ const defaultAuthenticationPlugins = (
6775
const magicLink = cloudflareMagicLink({
6876
email: bindings.EMAIL,
6977
from,
70-
admitEmail: waitlistAdmission(bindings.DB),
78+
admitEmail: async (email, callbackURL) =>
79+
(await inviteSignupAdmission(db, callbackURL)) ||
80+
(await waitlistAdmission(db)(email)),
7181
verifyRequest: async (token) => {
7282
const error = await Effect.runPromise(
7383
verifyTurnstile({
@@ -149,6 +159,7 @@ const workspaceOrganization = (bindings: Bindings) => {
149159

150160
return organization({
151161
organizationLimit: 1,
162+
invitationLimit: 0,
152163
disableOrganizationDeletion: true,
153164
allowUserToCreateOrganization: async (user) =>
154165
!(await workspaceMembershipExists(db, user.id)),
@@ -162,6 +173,17 @@ const workspaceOrganization = (bindings: Bindings) => {
162173
beforeAddMember: async ({ member }) => {
163174
await assertNoMembership(member.userId);
164175
},
176+
// Workspaces grow only through dx invite links (settings/members).
177+
beforeCreateInvitation: async () => {
178+
throw new APIError("FORBIDDEN", {
179+
message: "Use a workspace invite link.",
180+
});
181+
},
182+
beforeAcceptInvitation: async () => {
183+
throw new APIError("FORBIDDEN", {
184+
message: "Use a workspace invite link.",
185+
});
186+
},
165187
},
166188
});
167189
};

‎apps/core/src/auth/cloudflare-magic-link.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ export interface CloudflareMagicLinkOptions {
77
readonly email: SendEmail;
88
readonly from: string;
99
readonly verifyRequest?: (token: string | null) => Promise<void>;
10-
readonly admitEmail?: (email: string) => Promise<boolean>;
10+
readonly admitEmail?: (
11+
email: string,
12+
callbackURL: unknown,
13+
) => Promise<boolean>;
1114
}
1215

1316
const magicLinkToken = () =>
@@ -47,7 +50,7 @@ export const cloudflareMagicLink = ({
4750
if (
4851
typeof recipient === "string" &&
4952
admitEmail !== undefined &&
50-
!(await admitEmail(recipient))
53+
!(await admitEmail(recipient, context.body?.callbackURL))
5154
) {
5255
return context.json({ status: true });
5356
}

‎apps/core/src/auth/daemon-api-key.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
22
import { ThreadId, UserId } from "@dx/domain";
33
import { Effect, Option, Redacted, Schema } from "effect";
44
import type { Bindings } from "../http/types.js";
5+
import { threadProjectReachableSql } from "../settings/members/membership-sql.js";
56
import { createAuth } from "./better-auth.js";
67
import { loadAuthenticationRequirements } from "./requirements.js";
78

@@ -94,7 +95,10 @@ export const verifyThreadDaemonApiKey = async (
9495
.prepare(
9596
`SELECT 1 AS owned
9697
FROM threads
97-
WHERE id = ? AND owner_user_id = ? AND lifecycle_state = 'active'`,
98+
JOIN projects ON projects.id = threads.project_id
99+
WHERE threads.id = ? AND threads.owner_user_id = ?
100+
AND threads.lifecycle_state = 'active'
101+
AND ${threadProjectReachableSql("projects", "threads.owner_user_id")}`,
98102
)
99103
.bind(threadId, verified.key.referenceId)
100104
.first<{ readonly owned: number }>();
@@ -117,12 +121,14 @@ export const confirmThreadDaemonApiKey = async (
117121
`SELECT 1 AS owned
118122
FROM apikey
119123
JOIN threads ON threads.owner_user_id = apikey.referenceId
124+
JOIN projects ON projects.id = threads.project_id
120125
WHERE apikey.id = ?
121126
AND apikey.configId = ?
122127
AND (apikey.enabled IS NULL OR apikey.enabled = 1)
123128
AND apikey.expiresAt IS NULL
124129
AND threads.id = ?
125-
AND threads.lifecycle_state = 'active'`,
130+
AND threads.lifecycle_state = 'active'
131+
AND ${threadProjectReachableSql("projects", "threads.owner_user_id")}`,
126132
)
127133
.bind(keyId, DAEMON_KEY_CONFIG, threadId)
128134
.first<{ readonly owned: number }>();

‎apps/core/src/auth/email-password.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ const emailDomain = (email: string) => {
1414
export const emailPassword = (
1515
allowedSignupDomain: string | undefined,
1616
signupEnabled = false,
17+
admitInvitedSignup?: (callbackURL: unknown) => Promise<boolean>,
1718
): BetterAuthPlugin => ({
1819
id: "dx-email-password",
1920
init: () => ({
2021
options: {
2122
emailAndPassword: {
2223
enabled: true,
23-
disableSignUp: !signupEnabled,
24+
disableSignUp: !signupEnabled && admitInvitedSignup === undefined,
2425
autoSignIn: true,
2526
requireEmailVerification: false,
2627
},
@@ -39,6 +40,14 @@ export const emailPassword = (
3940
) {
4041
throw accountCreationUnavailable();
4142
}
43+
// A valid workspace invite link admits sign-up when public
44+
// sign-up is off.
45+
if (
46+
!signupEnabled &&
47+
!(await admitInvitedSignup?.(context.body?.callbackURL))
48+
) {
49+
throw accountCreationUnavailable();
50+
}
4251
}),
4352
},
4453
],

‎apps/core/src/execution/dxd/protocol.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
} from "../../thread-changes/candidate.js";
2525

2626
export const DXD_PROTOCOL_MAJOR = 2;
27-
export const DXD_RELEASE = "0.8.1";
27+
export const DXD_RELEASE = "0.8.2";
2828
export const DXD_TERMINAL_VERSION = 1;
2929
export const DXD_WORKLOAD_IDENTITY_VERSION = 1;
3030
export const DXD_HEARTBEAT_INTERVAL_MS = 2_000;

0 commit comments

Comments
 (0)