Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/pages/api/v2/auth/sign-up.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Sign up for a new account:780. If email/password matches an existing account, just sign them in. Reduces confusion.91. Reject if password is absurdly weak.102. Query the database to make sure the email address is not already taken.113. Generate a random account_id. Do not check it is not already taken, since that's12highly unlikely, and the insert in 4 would fail anyways.134. Write account to the database.145. Sign user in (if not being used via the API).1516This can also be used via the API, but the client must have a minimum balance17of at least - $100.181920API Usage:2122curl -u sk_abcdefQWERTY090900000000: \23-d firstName=John00 \24-d lastName=Doe00 \25-d [email protected] \26-d password=xyzabc09090 \27-d terms=true https://cocalc.com/api/v2/auth/sign-up2829TIP: If you want to pass in an email like [email protected], use '%2B' in place of '+'.30*/3132import { v4 } from "uuid";3334import { getServerSettings } from "@cocalc/database/settings/server-settings";35import createAccount from "@cocalc/server/accounts/create-account";36import isAccountAvailable from "@cocalc/server/auth/is-account-available";37import isDomainExclusiveSSO from "@cocalc/server/auth/is-domain-exclusive-sso";38import passwordStrength from "@cocalc/server/auth/password-strength";39import reCaptcha from "@cocalc/server/auth/recaptcha";40import redeemRegistrationToken from "@cocalc/server/auth/tokens/redeem";41import sendWelcomeEmail from "@cocalc/server/email/welcome-email";42import getSiteLicenseId from "@cocalc/server/public-paths/site-license-id";43import {44is_valid_email_address as isValidEmailAddress,45len,46} from "@cocalc/util/misc";4748import getAccountId from "lib/account/get-account";49import { apiRoute, apiRouteOperation } from "lib/api";50import assertTrusted from "lib/api/assert-trusted";51import getParams from "lib/api/get-params";52import {53SignUpInputSchema,54SignUpOutputSchema,55} from "lib/api/schema/accounts/sign-up";56import { SignUpIssues } from "lib/types/sign-up";57import { getAccount, signUserIn } from "./sign-in";5859export async function signUp(req, res) {60let {61terms,62email,63password,64firstName,65lastName,66registrationToken,67tags,68publicPathId,69signupReason,70} = getParams(req);7172password = (password ?? "").trim();73email = (email ?? "").toLowerCase().trim();74firstName = (firstName ? firstName : "Anonymous").trim();75lastName = (76lastName ? lastName : `User-${Math.round(Date.now() / 1000)}`77).trim();78registrationToken = (registrationToken ?? "").trim();7980// if email is empty, then trying to create an anonymous account,81// which may be allowed, depending on server settings.82const isAnonymous = !email;8384if (!isAnonymous && email && password) {85// Maybe there is already an account with this email and password?86try {87const account_id = await getAccount(email, password);88await signUserIn(req, res, account_id);89return;90} catch (_err) {91// fine -- just means they don't already have an account.92}93}9495if (!isAnonymous) {96const issues = checkObviousConditions({ terms, email, password });97if (len(issues) > 0) {98res.json({ issues });99return;100}101}102103// The UI doesn't let users try to make an account via signUp if104// email isn't enabled. However, they might try to directly POST105// to the API, so we check here as well.106const { email_signup, anonymous_signup, anonymous_signup_licensed_shares } =107await getServerSettings();108109const owner_id = await getAccountId(req);110if (owner_id) {111if (isAnonymous) {112res.json({113issues: {114api: "Creation of anonymous accounts via the API is not allowed.",115},116});117return;118}119// no captcha required -- api access120// We ONLY allow creation without checking the captcha121// for trusted users.122try {123await assertTrusted(owner_id);124} catch (err) {125res.json({126issues: {127api: `${err}`,128},129});130return;131}132} else {133try {134await reCaptcha(req);135} catch (err) {136res.json({137issues: {138reCaptcha: err.message,139},140});141return;142}143}144145if (isAnonymous) {146// Check anonymous sign up conditions.147if (!anonymous_signup) {148if (149anonymous_signup_licensed_shares &&150publicPathId &&151(await hasSiteLicenseId(publicPathId))152) {153// an unlisted public path with a license when anonymous_signup_licensed_shares is set is allowed154} else {155res.json({156issues: {157email: "Anonymous account creation is disabled.",158},159});160return;161}162}163} else {164// Check the email sign up conditions.165if (!email_signup) {166res.json({167issues: {168email: "Email account creation is disabled.",169},170});171return;172}173const exclusive = await isDomainExclusiveSSO(email);174if (exclusive) {175res.json({176issues: {177email: `To sign up with "@${exclusive}", you have to use the corresponding single sign on mechanism. Delete your email address above, then click the SSO icon.`,178},179});180return;181}182183if (!(await isAccountAvailable(email))) {184res.json({185issues: { email: `Email address "${email}" already in use.` },186});187return;188}189}190191try {192await redeemRegistrationToken(registrationToken);193} catch (err) {194res.json({195issues: {196registrationToken: `Issue with registration token -- ${err.message}`,197},198});199return;200}201202try {203const account_id = v4();204await createAccount({205email,206password,207firstName,208lastName,209account_id,210tags,211signupReason,212owner_id,213});214215if (email) {216try {217await sendWelcomeEmail(email, account_id);218} catch (err) {219// Expected to fail, e.g., when sendgrid or smtp not configured yet.220// TODO: should log using debug instead of console?221console.log(`WARNING: failed to send welcome email to ${email}`, err);222}223}224if (!owner_id) {225await signUserIn(req, res, account_id); // sets a cookie226}227res.json({ account_id });228} catch (err) {229res.json({ error: err.message });230}231}232233export function checkObviousConditions({234terms,235email,236password,237}): SignUpIssues {238const issues: SignUpIssues = {};239if (!terms) {240issues.terms = "You must agree to the terms of usage.";241}242if (!email || !isValidEmailAddress(email)) {243issues.email = `You must provide a valid email address -- '${email}' is not valid.`;244}245if (!password || password.length < 6) {246issues.password = "Your password must not be very easy to guess.";247} else {248const { score, help } = passwordStrength(password);249if (score <= 2) {250issues.password = help ? help : "Your password is too easy to guess.";251}252}253return issues;254}255256async function hasSiteLicenseId(id: string): Promise<boolean> {257return !!(await getSiteLicenseId(id));258}259260export default apiRoute({261signUp: apiRouteOperation({262method: "POST",263openApiOperation: {264tags: ["Accounts", "Admin"],265},266})267.input({268contentType: "application/json",269body: SignUpInputSchema,270})271.outputs([272{273status: 200,274contentType: "application/json",275body: SignUpOutputSchema,276},277])278.handler(signUp),279});280281282