Skip to content

Commit 1de8a3c

Browse files
committed
make it so the registration token can be used to set the customize field of the user account on creation
1 parent 5a5e7d8 commit 1de8a3c

File tree

9 files changed

+70
-9
lines changed

9 files changed

+70
-9
lines changed

src/packages/database/postgres/registration-tokens.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface Query {
1212
limit?: number;
1313
disabled?: boolean;
1414
ephemeral?: boolean;
15+
customize?;
1516
}
1617

1718
export default async function registrationTokensQuery(
@@ -38,24 +39,27 @@ export default async function registrationTokensQuery(
3839
return rows;
3940
} else if (query.token) {
4041
// upsert an existing one
41-
const { token, descr, expires, limit, disabled, ephemeral } = query;
42+
const { token, descr, expires, limit, disabled, ephemeral, customize } =
43+
query;
4244
const { rows } = await callback2(db._query, {
43-
query: `INSERT INTO registration_tokens ("token","descr","expires","limit","disabled","ephemeral")
44-
VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (token)
45+
query: `INSERT INTO registration_tokens ("token","descr","expires","limit","disabled","ephemeral","customize")
46+
VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (token)
4547
DO UPDATE SET
4648
"token" = EXCLUDED.token,
4749
"descr" = EXCLUDED.descr,
4850
"expires" = EXCLUDED.expires,
4951
"limit" = EXCLUDED.limit,
5052
"disabled" = EXCLUDED.disabled,
51-
"ephemeral" = EXCLUDED.ephemeral`,
53+
"ephemeral" = EXCLUDED.ephemeral,
54+
"customize" = EXCLUDED.customize`,
5255
params: [
5356
token,
5457
descr ? descr : null,
5558
expires ? expires : null,
5659
limit == null ? null : limit, // if undefined make it null
5760
disabled != null ? disabled : false,
5861
ephemeral == null ? null : ephemeral,
62+
customize == null ? null : customize,
5963
],
6064
});
6165
return rows;

src/packages/frontend/admin/registration-token.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
InputNumber,
1717
Popconfirm,
1818
Radio,
19+
Space,
1920
Switch,
2021
Table,
2122
} from "antd";
@@ -56,6 +57,10 @@ interface Token {
5657
counter?: number; // readonly
5758
expires?: dayjs.Dayjs; // DB uses Date objects, watch out!
5859
ephemeral?: number;
60+
customize?: {
61+
disableCollaborators?: boolean;
62+
disableAI?: boolean;
63+
};
5964
}
6065

6166
const HOUR_MS = 60 * 60 * 1000;
@@ -121,6 +126,7 @@ function use_registration_tokens() {
121126
limit: null,
122127
disabled: null,
123128
ephemeral: null,
129+
customize: null,
124130
},
125131
},
126132
});
@@ -178,11 +184,18 @@ function use_registration_tokens() {
178184
"limit",
179185
"descr",
180186
"ephemeral",
187+
"customize",
181188
] as RegistrationTokenSetFields[]);
182189
// set optional field to undefined (to get rid of it)
183190
["descr", "limit", "expires", "ephemeral"].forEach(
184191
(k: RegistrationTokenSetFields) => (val[k] = val[k] ?? undefined),
185192
);
193+
if (val.customize != null) {
194+
const { disableCollaborators, disableAI } = val.customize;
195+
if (!disableCollaborators && !disableAI) {
196+
val.customize = undefined;
197+
}
198+
}
186199
try {
187200
set_saving(true);
188201
await query({
@@ -416,6 +429,24 @@ export function RegistrationToken() {
416429
}}
417430
</Form.Item>
418431
</Form.Item>
432+
<Form.Item label="Restrictions">
433+
<Space direction="vertical">
434+
<Form.Item
435+
name={["customize", "disableCollaborators"]}
436+
valuePropName="checked"
437+
noStyle
438+
>
439+
<Checkbox>Disable configuring collaborators</Checkbox>
440+
</Form.Item>
441+
<Form.Item
442+
name={["customize", "disableAI"]}
443+
valuePropName="checked"
444+
noStyle
445+
>
446+
<Checkbox>Disable artificial intelligence</Checkbox>
447+
</Form.Item>
448+
</Space>
449+
</Form.Item>
419450
<Form.Item name="active" label="Active" valuePropName="checked">
420451
<Switch />
421452
</Form.Item>
@@ -544,6 +575,16 @@ export function RegistrationToken() {
544575
dataIndex="ephemeral"
545576
render={(value) => formatEphemeralHours(value)}
546577
/>
578+
<Table.Column<Token>
579+
title="Restrict collaborators"
580+
render={(_, token) =>
581+
token.customize?.disableCollaborators ? "Yes" : ""
582+
}
583+
/>
584+
<Table.Column<Token>
585+
title="Disable AI"
586+
render={(_, token) => (token.customize?.disableAI ? "Yes" : "")}
587+
/>
547588
<Table.Column<Token>
548589
title="% Used"
549590
dataIndex="used"

src/packages/next/pages/api/v2/auth/ephemeral.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default async function createEphemeralAccount(req, res) {
4747
tags: ["ephemeral"],
4848
signupReason: "ephemeral",
4949
ephemeral: tokenInfo.ephemeral,
50+
customize: tokenInfo.customize,
5051
});
5152
} catch (err) {
5253
res.json({

src/packages/next/pages/api/v2/auth/sign-up.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,9 @@ export async function signUp(req, res) {
194194
}
195195
}
196196

197+
let tokenInfo;
197198
try {
198-
await redeemRegistrationToken(registrationToken);
199+
tokenInfo = await redeemRegistrationToken(registrationToken);
199200
} catch (err) {
200201
res.json({
201202
issues: {
@@ -216,6 +217,8 @@ export async function signUp(req, res) {
216217
tags,
217218
signupReason,
218219
owner_id,
220+
ephemeral: tokenInfo?.ephemeral,
221+
customize: tokenInfo?.customize,
219222
});
220223

221224
if (email) {

src/packages/server/accounts/create-account.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface Params {
2828
// avoiding confusion with self-hosted installs.
2929
noFirstProject?: boolean;
3030
ephemeral?: number;
31+
customize?: any;
3132
}
3233

3334
export default async function createAccount({
@@ -41,6 +42,7 @@ export default async function createAccount({
4142
owner_id,
4243
noFirstProject,
4344
ephemeral,
45+
customize,
4446
}: Params): Promise<void> {
4547
try {
4648
log.debug(
@@ -54,7 +56,7 @@ export default async function createAccount({
5456
);
5557
const pool = getPool();
5658
await pool.query(
57-
"INSERT INTO accounts (email_address, password_hash, first_name, last_name, account_id, created, tags, sign_up_usage_intent, owner_id, ephemeral) VALUES($1::TEXT, $2::TEXT, $3::TEXT, $4::TEXT, $5::UUID, NOW(), $6::TEXT[], $7::TEXT, $8::UUID, $9::BIGINT)",
59+
"INSERT INTO accounts (email_address, password_hash, first_name, last_name, account_id, created, tags, sign_up_usage_intent, owner_id, ephemeral, customize) VALUES($1::TEXT, $2::TEXT, $3::TEXT, $4::TEXT, $5::UUID, NOW(), $6::TEXT[], $7::TEXT, $8::UUID, $9::BIGINT, $10::JSONB)",
5860
[
5961
email ? email : undefined, // can't insert "" more than once!
6062
password ? passwordHash(password) : undefined, // definitely don't set password_hash to hash of empty string, e.g., anonymous accounts can then NEVER switch to email/password. This was a bug in production for a while.
@@ -65,6 +67,7 @@ export default async function createAccount({
6567
signupReason,
6668
owner_id,
6769
ephemeral ?? null,
70+
customize ?? null,
6871
],
6972
);
7073
const { insecure_test_mode } = await getServerSettings();

src/packages/server/auth/tokens/redeem.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getTransactionClient } from "@cocalc/database/pool";
1212
export interface RegistrationTokenInfo {
1313
token: string;
1414
ephemeral?: number;
15+
customize?: any;
1516
}
1617

1718
export default async function redeem(
@@ -36,7 +37,7 @@ export default async function redeem(
3637
// → if counter, check counter vs. limit
3738
// → true: increase the counter → ok
3839
// → false: ok
39-
const q_match = `SELECT "expires", "counter", "limit", "disabled", "ephemeral"
40+
const q_match = `SELECT "expires", "counter", "limit", "disabled", "ephemeral", "customize"
4041
FROM registration_tokens
4142
WHERE token = $1::TEXT
4243
FOR UPDATE`;
@@ -52,6 +53,7 @@ export default async function redeem(
5253
limit,
5354
disabled: disabled_raw,
5455
ephemeral,
56+
customize,
5557
} = match.rows[0];
5658
const counter = counter_raw ?? 0;
5759
const disabled = disabled_raw ?? false;
@@ -79,6 +81,7 @@ export default async function redeem(
7981
return {
8082
token,
8183
ephemeral: typeof ephemeral === "number" ? ephemeral : undefined,
84+
customize,
8285
};
8386
} catch (err) {
8487
await client.query("ROLLBACK");

src/packages/util/db-schema/accounts.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,6 @@ Table({
579579
evaluate_key: true,
580580
font_size: true,
581581
profile: true,
582-
customize: true,
583582
ssh_keys: true,
584583
sign_up_usage_intent: true,
585584
unlisted: true,

src/packages/util/db-schema/registration-tokens.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Table({
4141
limit: null,
4242
disabled: null,
4343
ephemeral: null,
44+
customize: null,
4445
} as { [key in RegistrationTokenSetFields]: null },
4546
},
4647
get: {
@@ -55,6 +56,7 @@ Table({
5556
limit: null,
5657
disabled: null,
5758
ephemeral: null,
59+
customize: null,
5860
} as { [key in RegistrationTokenGetFields]: null },
5961
},
6062
},
@@ -73,5 +75,9 @@ Table({
7375
type: "number",
7476
desc: "optional – lifetime in milliseconds for accounts/projects created via this token",
7577
},
78+
customize: {
79+
type: "map",
80+
desc: "Optional account customization overrides applied when redeeming this token.",
81+
},
7682
},
7783
});

src/packages/util/db-schema/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ export type RegistrationTokenSetFields =
327327
| "expires"
328328
| "limit"
329329
| "disabled"
330-
| "ephemeral";
330+
| "ephemeral"
331+
| "customize";
331332

332333
export type RegistrationTokenGetFields = RegistrationTokenSetFields | "counter";
333334

0 commit comments

Comments
 (0)