feature: Adds user creation endpoint.
This commit is contained in:
parent
6d0fc93138
commit
35e23357ef
10 changed files with 197 additions and 4 deletions
25
src/index.ts
25
src/index.ts
|
@ -3,15 +3,34 @@ import process from "node:process";
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import Fastify from "fastify";
|
||||
|
||||
import { ServerOptions, SessionToken } from "./libs/types.js";
|
||||
import { route as create } from "./routes/user/create.js";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const isSignupEnabled: boolean = Boolean(process.env.IS_SIGNUP_ENABLED);
|
||||
const unsafeAdminSignup: boolean = Boolean(process.env.UNSAFE_ADMIN_SIGNUP);
|
||||
|
||||
const noUsersCheck: boolean = await prisma.user.count() == 0;
|
||||
|
||||
if (unsafeAdminSignup) {
|
||||
console.error("WARNING: You have admin sign up on! This means that anyone that signs up will have admin rights!");
|
||||
}
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
isSignupEnabled: isSignupEnabled ? true : noUsersCheck,
|
||||
isSignupAsAdminEnabled: unsafeAdminSignup ? true : noUsersCheck,
|
||||
|
||||
allowUnsafeGlobalTokens: process.env.NODE_ENV != "production"
|
||||
};
|
||||
|
||||
const sessionTokens: Record<number, SessionToken[]> = {};
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
});
|
||||
|
||||
fastify.get('/', async function handler (request, reply) {
|
||||
return { hello: 'world' };
|
||||
})
|
||||
create(fastify, prisma, sessionTokens, serverOptions);
|
||||
|
||||
// Run the server!
|
||||
try {
|
||||
|
|
20
src/libs/generateToken.ts
Normal file
20
src/libs/generateToken.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
function getRandomInt(min: number, max: number): number {
|
||||
const minCeiled = Math.ceil(min);
|
||||
const maxFloored = Math.floor(max);
|
||||
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive
|
||||
}
|
||||
|
||||
export function generateRandomData(length: number): string {
|
||||
let newString = "";
|
||||
|
||||
for (let i = 0; i < length; i += 2) {
|
||||
const randomNumber = getRandomInt(0, 255);
|
||||
newString += randomNumber.toString(16);
|
||||
}
|
||||
|
||||
return newString;
|
||||
}
|
||||
|
||||
export function generateToken() {
|
||||
return generateRandomData(128);
|
||||
}
|
14
src/libs/types.ts
Normal file
14
src/libs/types.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
export type ServerOptions = {
|
||||
isSignupEnabled: boolean;
|
||||
isSignupAsAdminEnabled: boolean;
|
||||
|
||||
allowUnsafeGlobalTokens: boolean;
|
||||
}
|
||||
|
||||
// NOTE: Someone should probably use Redis for this, but this is fine...
|
||||
export type SessionToken = {
|
||||
createdAt: number,
|
||||
expiresAt: number, // Should be (createdAt + (30 minutes))
|
||||
|
||||
token: string
|
||||
};
|
92
src/routes/user/create.ts
Normal file
92
src/routes/user/create.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import type { PrismaClient } from "@prisma/client";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
|
||||
import { hash } from "bcrypt";
|
||||
|
||||
import { ServerOptions, SessionToken } from "../../libs/types.js";
|
||||
import { generateToken } from "../../libs/generateToken.js";
|
||||
|
||||
export function route(fastify: FastifyInstance, prisma: PrismaClient, tokens: Record<number, SessionToken[]>, options: ServerOptions) {
|
||||
// TODO: Permissions
|
||||
|
||||
fastify.post("/api/v1/users/create", {
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name", "email", "password"],
|
||||
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
email: { type: "string" },
|
||||
password: { type: "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}, async(req, res) => {
|
||||
// @ts-ignore
|
||||
const body: {
|
||||
name: string,
|
||||
email: string,
|
||||
password: string
|
||||
} = req.body;
|
||||
|
||||
if (!options.isSignupEnabled) {
|
||||
return res.status(400).send({
|
||||
error: "Signing up is not enabled at this time."
|
||||
});
|
||||
};
|
||||
|
||||
const userSearch = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: body.email
|
||||
}
|
||||
});
|
||||
|
||||
if (userSearch) {
|
||||
return res.status(400).send({
|
||||
error: "User already exists"
|
||||
})
|
||||
};
|
||||
|
||||
const saltedPassword: string = await hash(body.password, 15);
|
||||
|
||||
let userData = {
|
||||
name: body.name,
|
||||
email: body.email,
|
||||
password: saltedPassword,
|
||||
rootToken: null
|
||||
};
|
||||
|
||||
if (options.allowUnsafeGlobalTokens) {
|
||||
userData.rootToken = generateToken() as unknown as null;
|
||||
}
|
||||
|
||||
const userCreateResults = await prisma.user.create({
|
||||
data: userData
|
||||
});
|
||||
|
||||
// FIXME(?): Redundant checks
|
||||
if (options.allowUnsafeGlobalTokens) {
|
||||
return {
|
||||
success: true,
|
||||
token: userCreateResults.rootToken
|
||||
};
|
||||
} else {
|
||||
const generatedToken = generateToken();
|
||||
|
||||
tokens[userCreateResults.id] = [];
|
||||
|
||||
tokens[userCreateResults.id].push({
|
||||
createdAt: Date.now(),
|
||||
expiresAt: Date.now() + (30 * 60_000),
|
||||
|
||||
token: generatedToken
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
token: generatedToken
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue