feat: add signup disable config and fix telemetry startup (#3319)

* feat: add signup disable config and fix telemetry startup

* fix: nits for config spread and 403 checks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Daniel Salazar <daniel.salazar@puter.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mouaid
2026-07-07 13:52:29 -07:00
committed by GitHub
co-authored by Claude Fable 5 Daniel Salazar
parent a04d5fa5e0
commit 0801e1dc44
12 changed files with 219 additions and 0 deletions
@@ -586,6 +586,24 @@ describe('AuthController.handleSignup', () => {
);
});
it('rejects brand-new temp signups when registration is disabled', async () => {
const authConfig = server.controllers.auth.config as {
disable_user_signup?: boolean;
};
const prev = authConfig.disable_user_signup;
authConfig.disable_user_signup = true;
try {
await expect(
controller.handleSignup(makeReq({ is_temp: true }), makeRes()),
).rejects.toMatchObject({
statusCode: 403,
legacyCode: 'signup_disabled',
});
} finally {
authConfig.disable_user_signup = prev;
}
});
it('emits puter.signup.success on successful signup', async () => {
const baseline = heardSignupSuccess.length;
const username = `s_${uniq()}`;
@@ -607,6 +625,90 @@ describe('AuthController.handleSignup', () => {
),
).toBe(true);
});
it('still allows claiming a pseudo-user row when registration is disabled', async () => {
const authConfig = server.controllers.auth.config as {
disable_user_signup?: boolean;
};
const prev = authConfig.disable_user_signup;
authConfig.disable_user_signup = true;
try {
const targetEmail = `disabled_claim_${uniq()}@test.local`;
const placeholder = await server.stores.user.create({
username: `placeholder_${uniq()}`,
uuid: uuidv4(),
password: null,
email: targetEmail,
clean_email: targetEmail,
email_confirmed: 0,
} as never);
const res = makeRes();
await controller.handleSignup(
makeReq({
username: `claim_${uniq()}`,
email: targetEmail,
password: 'correct-horse-battery',
}),
res,
);
expect(isCompleteLoginResponse(res.body)).toBe(true);
const claimed = await server.stores.user.getById(placeholder.id, {
force: true,
});
expect(claimed!.username).not.toBe(placeholder.username);
} finally {
authConfig.disable_user_signup = prev;
}
});
it('does not reveal existing usernames or emails when registration is disabled', async () => {
const username = `taken_${uniq()}`;
const email = `${username}@test.local`;
await controller.handleSignup(
makeReq({ username, email, password: 'correct-horse-battery' }),
makeRes(),
);
const authConfig = server.controllers.auth.config as {
disable_user_signup?: boolean;
};
const prev = authConfig.disable_user_signup;
authConfig.disable_user_signup = true;
try {
// Taken username → the generic 403, not the duplicate error.
await expect(
controller.handleSignup(
makeReq({
username,
email: `fresh_${uniq()}@test.local`,
password: 'correct-horse-battery',
}),
makeRes(),
),
).rejects.toMatchObject({
statusCode: 403,
legacyCode: 'signup_disabled',
});
// Taken (non-claimable) email → same generic 403.
await expect(
controller.handleSignup(
makeReq({
username: `fresh_${uniq()}`,
email,
password: 'correct-horse-battery',
}),
makeRes(),
),
).rejects.toMatchObject({
statusCode: 403,
legacyCode: 'signup_disabled',
});
} finally {
authConfig.disable_user_signup = prev;
}
});
});
// -- Signup device signal (fingerprint) --
@@ -522,6 +522,31 @@ export class AuthController extends PuterController {
}
}
// Signup-disabled gate. Runs before the duplicate checks so a
// disabled endpoint doesn't reveal which usernames or emails
// exist. Claiming a pre-existing placeholder row is still
// allowed, so permanent signups look the email up first.
if (this.config.disable_user_signup) {
let claimable = false;
if (!is_temp) {
const existing =
(await this.stores.user.getByEmail(body.email)) ??
(await this.stores.user.getByCleanEmail(
cleanEmail(body.email),
));
claimable = Boolean(
existing &&
!existing.email_confirmed &&
existing.password === null,
);
}
if (!claimable) {
throw new HttpError(403, 'User registration is disabled.', {
legacyCode: 'signup_disabled',
});
}
}
// Duplicate username check
if (await this.stores.user.getByUsername(body.username)) {
throw new HttpError(