firefish/packages/backend/src/server/api/common/signup.ts

137 lines
3.2 KiB
TypeScript
Raw Normal View History

2023-01-13 04:40:33 +00:00
import { generateKeyPair } from "node:crypto";
import generateUserToken from "./generate-native-user-token.js";
import { User } from "@/models/entities/user.js";
import { Users, UsedUsernames } from "@/models/index.js";
import { UserProfile } from "@/models/entities/user-profile.js";
import { IsNull } from "typeorm";
import { genId, hashPassword, toPuny } from "backend-rs";
2023-01-13 04:40:33 +00:00
import { UserKeypair } from "@/models/entities/user-keypair.js";
import { UsedUsername } from "@/models/entities/used-username.js";
import { db } from "@/db/postgre.js";
import config from "@/config/index.js";
export async function signup(opts: {
2023-01-13 04:40:33 +00:00
username: User["username"];
password?: string | null;
2023-01-13 04:40:33 +00:00
passwordHash?: UserProfile["password"] | null;
host?: string | null;
}) {
const { username, password, passwordHash, host } = opts;
let hash = passwordHash;
2022-10-31 04:38:20 +00:00
const userCount = await Users.countBy({
host: IsNull(),
});
if (config.maxUserSignups != null && userCount > config.maxUserSignups) {
2023-01-13 04:40:33 +00:00
throw new Error("MAX_USERS_REACHED");
2022-10-31 04:38:20 +00:00
}
// Validate username
if (!Users.validateLocalUsername(username)) {
2023-01-13 04:40:33 +00:00
throw new Error("INVALID_USERNAME");
}
if (password != null && passwordHash == null) {
// Validate password
if (!Users.validatePassword(password)) {
2023-01-13 04:40:33 +00:00
throw new Error("INVALID_PASSWORD");
}
// Generate hash of password
hash = hashPassword(password);
}
// Generate secret
const secret = generateUserToken();
// Check username duplication
2023-01-13 04:40:33 +00:00
if (
await Users.findOneBy({
usernameLower: username.toLowerCase(),
host: IsNull(),
})
) {
throw new Error("DUPLICATED_USERNAME");
}
// Check deleted username duplication
if (await UsedUsernames.findOneBy({ username: username.toLowerCase() })) {
2023-01-13 04:40:33 +00:00
throw new Error("USED_USERNAME");
}
const keyPair = await new Promise<string[]>((res, rej) =>
2023-01-13 04:40:33 +00:00
generateKeyPair(
"rsa",
{
modulusLength: 4096,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
cipher: undefined,
passphrase: undefined,
},
} as any,
(err, publicKey, privateKey) =>
err ? rej(err) : res([publicKey, privateKey]),
),
);
let account!: User;
// Start transaction
2023-01-13 04:40:33 +00:00
await db.transaction(async (transactionalEntityManager) => {
const exist = await transactionalEntityManager.findOneBy(User, {
usernameLower: username.toLowerCase(),
host: IsNull(),
});
2023-01-13 04:40:33 +00:00
if (exist) throw new Error(" the username is already used");
account = await transactionalEntityManager.save(
new User({
id: genId(),
createdAt: new Date(),
username: username,
usernameLower: username.toLowerCase(),
host: host == null ? null : toPuny(host),
2023-01-13 04:40:33 +00:00
token: secret,
isAdmin:
(await Users.countBy({
host: IsNull(),
isAdmin: true,
2023-01-13 04:40:33 +00:00
})) === 0,
}),
);
await transactionalEntityManager.save(
new UserKeypair({
publicKey: keyPair[0],
privateKey: keyPair[1],
userId: account.id,
}),
);
await transactionalEntityManager.save(
new UserProfile({
userId: account.id,
autoAcceptFollowed: true,
password: hash,
}),
);
await transactionalEntityManager.save(
new UsedUsername({
createdAt: new Date(),
username: username.toLowerCase(),
}),
);
});
return { account, secret };
}