Skip to content

Commit ae98a65

Browse files
committed
auth: add configurable legacy signup redirect
Add schema-driven admin settings for replacing the normal sign-up form with a configurable notice and destination URL while optionally preserving a secondary path to reveal the legacy form. Expose matching public customize fields and render a reusable auth notice on sign-up/sign-in pages. This keeps the feature disabled by default for on-prem deployments and lets operators configure cocalc.com-specific migration copy from admin settings instead of hardcoding it in the auth pages.
1 parent d0039f7 commit ae98a65

6 files changed

Lines changed: 246 additions & 2 deletions

File tree

src/packages/database/settings/customize.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ export default async function getCustomize(
5959
anonymousSignupLicensedShares: settings.anonymous_signup_licensed_shares,
6060
emailSignup: settings.email_signup,
6161
accountCreationInstructions: settings.account_creation_email_instructions,
62+
accountCreationRedirectEnabled:
63+
settings.account_creation_redirect_enabled,
64+
accountCreationRedirectMessage:
65+
settings.account_creation_redirect_message,
66+
accountCreationRedirectUrl: settings.account_creation_redirect_url,
67+
accountCreationRedirectLinkText:
68+
settings.account_creation_redirect_link_text,
69+
accountCreationRedirectAllowLegacy:
70+
settings.account_creation_redirect_allow_legacy,
71+
accountCreationRedirectLegacyButtonText:
72+
settings.account_creation_redirect_legacy_button_text,
73+
signInBannerEnabled: settings.sign_in_banner_enabled,
74+
signInBannerMessage: settings.sign_in_banner_message,
75+
signInBannerUrl: settings.sign_in_banner_url,
76+
signInBannerLinkText: settings.sign_in_banner_link_text,
6277

6378
logoSquareURL: settings.logo_square,
6479
logoRectangularURL: settings.logo_rectangular,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* This file is part of CoCalc: Copyright © 2026 Sagemath, Inc.
3+
* License: MS-RSL – see LICENSE.md for details
4+
*/
5+
6+
import { Alert, Button, Space } from "antd";
7+
import type { ReactNode } from "react";
8+
9+
import Markdown from "@cocalc/frontend/editors/slate/static-markdown";
10+
11+
interface AuthNoticeProps {
12+
message?: string;
13+
url?: string;
14+
linkText?: string;
15+
defaultMessage: string;
16+
extra?: ReactNode;
17+
}
18+
19+
export default function AuthNotice({
20+
message,
21+
url,
22+
linkText,
23+
defaultMessage,
24+
extra,
25+
}: AuthNoticeProps) {
26+
const body = message?.trim() || defaultMessage;
27+
const href = url?.trim();
28+
return (
29+
<Alert
30+
type="info"
31+
showIcon
32+
style={{ margin: "15px 0 25px" }}
33+
message={<Markdown value={body} />}
34+
description={
35+
(href || extra) && (
36+
<Space wrap>
37+
{href && (
38+
<Button type="primary" href={href}>
39+
{linkText?.trim() || "Continue"}
40+
</Button>
41+
)}
42+
{extra}
43+
</Space>
44+
)
45+
}
46+
/>
47+
);
48+
}

src/packages/next/components/auth/sign-in.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import Contact from "components/landing/contact";
1515
import A from "components/misc/A";
1616
import apiPost from "lib/api/post";
1717
import useCustomize from "lib/use-customize";
18+
import AuthNotice from "./auth-notice";
1819
import AuthPageContainer from "./fragments/auth-page-container";
1920
import SSO, { RequiredSSO, useRequiredSSO } from "./sso";
2021
import { MAX_PASSWORD_LENGTH } from "@cocalc/util/auth";
@@ -43,8 +44,16 @@ export default function SignIn(props: SignInProps) {
4344

4445
function SignIn0(props: SignInProps) {
4546
const { minimal = false, onSuccess, showSignUp, signUpAction } = props;
46-
const { anonymousSignup, reCaptchaKey, siteName, strategies } =
47-
useCustomize();
47+
const {
48+
anonymousSignup,
49+
reCaptchaKey,
50+
siteName,
51+
strategies,
52+
signInBannerEnabled,
53+
signInBannerMessage,
54+
signInBannerUrl,
55+
signInBannerLinkText,
56+
} = useCustomize();
4857
const [email, setEmail] = useState<string>("");
4958
const [password, setPassword] = useState<string>("");
5059
const [signingIn, setSigningIn] = useState<boolean>(false);
@@ -136,6 +145,14 @@ function SignIn0(props: SignInProps) {
136145
minimal={minimal}
137146
title={`Sign in to ${siteName}`}
138147
>
148+
{!minimal && signInBannerEnabled && (
149+
<AuthNotice
150+
defaultMessage="Use the recommended site for new accounts and new work."
151+
message={signInBannerMessage}
152+
url={signInBannerUrl}
153+
linkText={signInBannerLinkText}
154+
/>
155+
)}
139156
<div style={{ margin: "10px 0" }}>
140157
{strategies == null
141158
? "Sign in"

src/packages/next/components/auth/sign-up.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import A from "components/misc/A";
4040
import Loading from "components/share/loading";
4141
import apiPost from "lib/api/post";
4242
import useCustomize from "lib/use-customize";
43+
import AuthNotice from "./auth-notice";
4344
import AuthPageContainer from "./fragments/auth-page-container";
4445
import SSO, { RequiredSSO, useRequiredSSO } from "./sso";
4546
import Tags from "./tags";
@@ -90,6 +91,12 @@ function SignUp0({
9091
siteName,
9192
emailSignup,
9293
accountCreationInstructions,
94+
accountCreationRedirectEnabled,
95+
accountCreationRedirectMessage,
96+
accountCreationRedirectUrl,
97+
accountCreationRedirectLinkText,
98+
accountCreationRedirectAllowLegacy,
99+
accountCreationRedirectLegacyButtonText,
93100
reCaptchaKey,
94101
onCoCalcCom,
95102
} = useCustomize();
@@ -106,6 +113,7 @@ function SignUp0({
106113
help?: string;
107114
}>({ score: 0 });
108115
const [checkingPassword, setCheckingPassword] = useState<boolean>(false);
116+
const [showLegacySignup, setShowLegacySignup] = useState<boolean>(false);
109117
const [issues, setIssues] = useState<{
110118
email?: string;
111119
password?: string;
@@ -247,6 +255,10 @@ function SignUp0({
247255
checkPasswordStrength,
248256
);
249257

258+
if (!minimal && accountCreationRedirectEnabled && !showLegacySignup) {
259+
return renderAccountCreationRedirect();
260+
}
261+
250262
if (!emailSignup && strategies.length == 0) {
251263
return (
252264
<Alert
@@ -323,6 +335,32 @@ function SignUp0({
323335
);
324336
}
325337

338+
function renderAccountCreationRedirect() {
339+
return (
340+
<AuthPageContainer
341+
error={renderError()}
342+
footer={renderFooter()}
343+
minimal={minimal}
344+
title={`Create an account with ${siteName}`}
345+
>
346+
<AuthNotice
347+
defaultMessage="New accounts should be created on the recommended site. Existing users can still sign in here."
348+
message={accountCreationRedirectMessage}
349+
url={accountCreationRedirectUrl}
350+
linkText={accountCreationRedirectLinkText}
351+
extra={
352+
accountCreationRedirectAllowLegacy ? (
353+
<Button onClick={() => setShowLegacySignup(true)}>
354+
{accountCreationRedirectLegacyButtonText?.trim() ||
355+
"Create an account here anyway"}
356+
</Button>
357+
) : undefined
358+
}
359+
/>
360+
</AuthPageContainer>
361+
);
362+
}
363+
326364
return (
327365
<AuthPageContainer
328366
error={renderError()}
@@ -331,6 +369,14 @@ function SignUp0({
331369
minimal={minimal}
332370
title={`Create a free account with ${siteName}`}
333371
>
372+
{!minimal && accountCreationRedirectEnabled && showLegacySignup && (
373+
<AuthNotice
374+
defaultMessage="New accounts should be created on the recommended site. Existing users can still sign in here."
375+
message={accountCreationRedirectMessage}
376+
url={accountCreationRedirectUrl}
377+
linkText={accountCreationRedirectLinkText}
378+
/>
379+
)}
334380
<Paragraph>
335381
By creating an account, you agree to the{" "}
336382
<A external={true} href="/policies/terms">

src/packages/util/db-schema/server-settings.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ export interface Customize {
108108
anonymousSignupLicensedShares?: boolean;
109109
emailSignup?: boolean;
110110
accountCreationInstructions?: string;
111+
accountCreationRedirectEnabled?: boolean;
112+
accountCreationRedirectMessage?: string;
113+
accountCreationRedirectUrl?: string;
114+
accountCreationRedirectLinkText?: string;
115+
accountCreationRedirectAllowLegacy?: boolean;
116+
accountCreationRedirectLegacyButtonText?: string;
117+
signInBannerEnabled?: boolean;
118+
signInBannerMessage?: string;
119+
signInBannerUrl?: string;
120+
signInBannerLinkText?: string;
111121
zendesk?: boolean; // true if zendesk support is configured.
112122
stripePublishableKey?: string;
113123
imprint_html?: string;

src/packages/util/db-schema/site-defaults.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ export type SiteSettingsKeys =
5656
| "site_name"
5757
| "site_description"
5858
| "account_creation_email_instructions"
59+
| "account_creation_redirect_enabled"
60+
| "account_creation_redirect_message"
61+
| "account_creation_redirect_url"
62+
| "account_creation_redirect_link_text"
63+
| "account_creation_redirect_allow_legacy"
64+
| "account_creation_redirect_legacy_button_text"
65+
| "sign_in_banner_enabled"
66+
| "sign_in_banner_message"
67+
| "sign_in_banner_url"
68+
| "sign_in_banner_link_text"
5969
| "help_email"
6070
| "logo_square"
6171
| "logo_rectangular"
@@ -441,6 +451,104 @@ export const site_settings_conf: SiteSettings = {
441451
show: show_theming_vars,
442452
tags: ["Theme"],
443453
},
454+
account_creation_redirect_enabled: {
455+
name: "Account creation redirect",
456+
desc: "Replace the normal sign-up form with a configurable notice and link. If legacy account creation is allowed below, users can still reveal the original form.",
457+
default: "no",
458+
valid: only_booleans,
459+
to_val: to_bool,
460+
show: show_theming_vars,
461+
tags: ["Theme"],
462+
},
463+
account_creation_redirect_message: {
464+
name: "Account creation redirect message",
465+
desc: "Markdown notice shown instead of the normal sign-up form when account creation redirect is enabled.",
466+
default:
467+
"New accounts should be created on the recommended site. Existing users can still sign in here.",
468+
clearable: true,
469+
show: (conf) =>
470+
show_theming_vars(conf) && to_bool(conf.account_creation_redirect_enabled),
471+
multiline: 3,
472+
tags: ["Theme"],
473+
},
474+
account_creation_redirect_url: {
475+
name: "Account creation redirect URL",
476+
desc: "Primary destination for new account creation when account creation redirect is enabled.",
477+
default: "",
478+
clearable: true,
479+
show: (conf) =>
480+
show_theming_vars(conf) && to_bool(conf.account_creation_redirect_enabled),
481+
to_val: to_trimmed_str,
482+
tags: ["Theme"],
483+
},
484+
account_creation_redirect_link_text: {
485+
name: "Account creation redirect link text",
486+
desc: "Text for the primary redirect button.",
487+
default: "Create an account on the recommended site",
488+
clearable: true,
489+
show: (conf) =>
490+
show_theming_vars(conf) && to_bool(conf.account_creation_redirect_enabled),
491+
tags: ["Theme"],
492+
},
493+
account_creation_redirect_allow_legacy: {
494+
name: "Allow legacy account creation",
495+
desc: "When account creation redirect is enabled, still allow users to reveal the original sign-up form.",
496+
default: "yes",
497+
valid: only_booleans,
498+
to_val: to_bool,
499+
show: (conf) =>
500+
show_theming_vars(conf) && to_bool(conf.account_creation_redirect_enabled),
501+
tags: ["Theme"],
502+
},
503+
account_creation_redirect_legacy_button_text: {
504+
name: "Legacy account creation button text",
505+
desc: "Text for the secondary button that reveals the original sign-up form.",
506+
default: "Create an account here anyway",
507+
clearable: true,
508+
show: (conf) =>
509+
show_theming_vars(conf) &&
510+
to_bool(conf.account_creation_redirect_enabled) &&
511+
to_bool(conf.account_creation_redirect_allow_legacy),
512+
tags: ["Theme"],
513+
},
514+
sign_in_banner_enabled: {
515+
name: "Sign-in banner",
516+
desc: "Show a configurable banner at the top of the sign-in form.",
517+
default: "no",
518+
valid: only_booleans,
519+
to_val: to_bool,
520+
show: show_theming_vars,
521+
tags: ["Theme"],
522+
},
523+
sign_in_banner_message: {
524+
name: "Sign-in banner message",
525+
desc: "Markdown notice shown above the sign-in form when the sign-in banner is enabled.",
526+
default: "",
527+
clearable: true,
528+
show: (conf) =>
529+
show_theming_vars(conf) && to_bool(conf.sign_in_banner_enabled),
530+
multiline: 3,
531+
tags: ["Theme"],
532+
},
533+
sign_in_banner_url: {
534+
name: "Sign-in banner URL",
535+
desc: "Optional link destination shown in the sign-in banner.",
536+
default: "",
537+
clearable: true,
538+
show: (conf) =>
539+
show_theming_vars(conf) && to_bool(conf.sign_in_banner_enabled),
540+
to_val: to_trimmed_str,
541+
tags: ["Theme"],
542+
},
543+
sign_in_banner_link_text: {
544+
name: "Sign-in banner link text",
545+
desc: "Text for the optional sign-in banner link.",
546+
default: "Go to the recommended site",
547+
clearable: true,
548+
show: (conf) =>
549+
show_theming_vars(conf) && to_bool(conf.sign_in_banner_enabled),
550+
tags: ["Theme"],
551+
},
444552
organization_name: {
445553
name: "Organization name",
446554
desc: "The name of your organization, e.g. 'Hogwarts School of Witchcraft and Wizardry'.",

0 commit comments

Comments
 (0)