Replace all async reads and writes to the JSON database with the sync reads and writes to prevent a data corruption race condition where two requests are processed at the same time and write to the same file, or one reads while the other writes causing read of partially written data.
97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
/*
|
|
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
import { readUsers, writeUsers, nextUserId, type ServerUser, readAuthenticationMethods, nextAuthenticationMethodId, writeAuthenticationMethods, nextEventId } from "~/server/database";
|
|
import { broadcastEvent } from "~/server/streams";
|
|
import type { ApiSession } from "~/shared/types/api";
|
|
|
|
export default defineEventHandler(async (event): Promise<ApiSession> => {
|
|
let session = await getServerSession(event, false);
|
|
if (session?.accountId !== undefined) {
|
|
throw createError({
|
|
status: 409,
|
|
message: "Cannot create account while logged in to an account."
|
|
});
|
|
}
|
|
|
|
const body = await readBody(event);
|
|
const name = body?.name;
|
|
|
|
const users = readUsers();
|
|
let user: ServerUser;
|
|
if (typeof name === "string") {
|
|
if (name === "") {
|
|
throw createError({
|
|
status: 400,
|
|
message: "Name cannot be blank",
|
|
});
|
|
}
|
|
if (users.some(user => user.name && user.name.toLowerCase() === name.toLowerCase())) {
|
|
throw createError({
|
|
status: 409,
|
|
message: "User already exists",
|
|
});
|
|
}
|
|
|
|
const firstUser = users.every(user => user.type === "anonymous");
|
|
user = {
|
|
id: nextUserId(),
|
|
updatedAt: new Date().toISOString(),
|
|
type: firstUser ? "admin" : "regular",
|
|
name,
|
|
};
|
|
|
|
} else if (name === undefined) {
|
|
user = {
|
|
id: nextUserId(),
|
|
updatedAt: new Date().toISOString(),
|
|
type: "anonymous",
|
|
};
|
|
} else {
|
|
throw createError({
|
|
status: 400,
|
|
message: "Invalid name",
|
|
});
|
|
}
|
|
|
|
if (user.type !== "anonymous") {
|
|
if (!session?.authenticationProvider) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: "Conflict",
|
|
message: "User account need an authentication method associated with it.",
|
|
});
|
|
}
|
|
const authMethods = await readAuthenticationMethods();
|
|
const method = authMethods.find(method => (
|
|
method.provider === session.authenticationProvider
|
|
&& method.slug === session.authenticationSlug
|
|
));
|
|
if (method) {
|
|
throw createError({
|
|
statusCode: 409,
|
|
statusMessage: "Conflict",
|
|
message: "A user is already associated with the authentication method",
|
|
});
|
|
}
|
|
authMethods.push({
|
|
id: nextAuthenticationMethodId(),
|
|
userId: user.id,
|
|
provider: session.authenticationProvider,
|
|
slug: session.authenticationSlug!,
|
|
name: session.authenticationName!,
|
|
})
|
|
writeAuthenticationMethods(authMethods);
|
|
}
|
|
|
|
users.push(user);
|
|
writeUsers(users);
|
|
await broadcastEvent({
|
|
id: nextEventId(),
|
|
type: "user-update",
|
|
data: user,
|
|
});
|
|
const newSession = await setServerSession(event, user);
|
|
return await serverSessionToApi(event, newSession);
|
|
})
|