forked from atums.world/atums.world
update username regex, normalize usernames, add user info query
This commit is contained in:
parent
774c8e22ce
commit
6fdc82dd49
6 changed files with 127 additions and 7 deletions
|
@ -66,7 +66,7 @@ export const userNameRestrictions: {
|
||||||
regex: RegExp;
|
regex: RegExp;
|
||||||
} = {
|
} = {
|
||||||
length: { min: 3, max: 20 },
|
length: { min: 3, max: 20 },
|
||||||
regex: /^[\p{L}0-9._-]+$/u,
|
regex: /^[\p{L}\p{N}._-]+$/u,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const passwordRestrictions: {
|
export const passwordRestrictions: {
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
"@types/bun": "^1.2.4",
|
"@types/bun": "^1.2.4",
|
||||||
"@types/ejs": "^3.1.5",
|
"@types/ejs": "^3.1.5",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
"@typescript-eslint/eslint-plugin": "^8.26.0",
|
||||||
"@typescript-eslint/parser": "^8.25.0",
|
"@typescript-eslint/parser": "^8.26.0",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-promise": "^7.2.1",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
"eslint-plugin-unicorn": "^56.0.1",
|
"eslint-plugin-unicorn": "^56.0.1",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"prettier": "^3.5.2"
|
"prettier": "^3.5.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|
|
@ -53,6 +53,7 @@ async function handler(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const normalizedUsername: string = username.normalize("NFC");
|
||||||
const reservation: ReservedSQL = await sql.reserve();
|
const reservation: ReservedSQL = await sql.reserve();
|
||||||
let firstUser: boolean = false;
|
let firstUser: boolean = false;
|
||||||
let inviteData: Invite | null = null;
|
let inviteData: Invite | null = null;
|
||||||
|
@ -91,7 +92,7 @@ async function handler(
|
||||||
const result: { usernameExists: boolean; emailExists: boolean }[] =
|
const result: { usernameExists: boolean; emailExists: boolean }[] =
|
||||||
await reservation`
|
await reservation`
|
||||||
SELECT
|
SELECT
|
||||||
EXISTS(SELECT 1 FROM users WHERE LOWER(username) = LOWER(${username})) AS "usernameExists",
|
EXISTS(SELECT 1 FROM users WHERE LOWER(username) = LOWER(${normalizedUsername})) AS "usernameExists",
|
||||||
EXISTS(SELECT 1 FROM users WHERE LOWER(email) = LOWER(${email})) AS "emailExists";
|
EXISTS(SELECT 1 FROM users WHERE LOWER(email) = LOWER(${email})) AS "emailExists";
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ async function handler(
|
||||||
try {
|
try {
|
||||||
const result: User[] = await reservation`
|
const result: User[] = await reservation`
|
||||||
INSERT INTO users (username, email, password, invited_by, roles, timezone)
|
INSERT INTO users (username, email, password, invited_by, roles, timezone)
|
||||||
VALUES (${username}, ${email}, ${hashedPassword}, ${inviteData?.created_by}, ARRAY[${roles.join(",")}]::TEXT[], ${defaultTimezone})
|
VALUES (${normalizedUsername}, ${email}, ${hashedPassword}, ${inviteData?.created_by}, ARRAY[${roles.join(",")}]::TEXT[], ${defaultTimezone})
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
103
src/routes/api/user/info[query].ts
Normal file
103
src/routes/api/user/info[query].ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { isValidUsername } from "@config/sql/users";
|
||||||
|
import { sql } from "bun";
|
||||||
|
|
||||||
|
import { isUUID } from "@/helpers/char";
|
||||||
|
import { logger } from "@/helpers/logger";
|
||||||
|
|
||||||
|
const routeDef: RouteDef = {
|
||||||
|
method: "GET",
|
||||||
|
accepts: "*/*",
|
||||||
|
returns: "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handler(request: ExtendedRequest): Promise<Response> {
|
||||||
|
const { query } = request.params as { query: string };
|
||||||
|
const { invites: showInvites } = request.query as { invites: string };
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 400,
|
||||||
|
error: "Username or user ID is required",
|
||||||
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user: GetUser | null = null;
|
||||||
|
let isSelf: boolean = false;
|
||||||
|
const isId: boolean = isUUID(query);
|
||||||
|
const normalized: string = isId ? query : query.normalize("NFC");
|
||||||
|
const isAdmin: boolean = request.session
|
||||||
|
? request.session.roles.includes("admin")
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (!isId && !isValidUsername(normalized).valid) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 400,
|
||||||
|
error: "Invalid username",
|
||||||
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result: GetUser[] = isId
|
||||||
|
? await sql`SELECT * FROM users WHERE id = ${normalized}`
|
||||||
|
: await sql`SELECT * FROM users WHERE username = ${normalized}`;
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 404,
|
||||||
|
error: "User not found",
|
||||||
|
},
|
||||||
|
{ status: 404 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
user = result[0];
|
||||||
|
isSelf = request.session ? user.id === request.session.id : false;
|
||||||
|
|
||||||
|
if (showInvites === "true" && (isAdmin || isSelf)) {
|
||||||
|
const invites: Invite[] =
|
||||||
|
await sql`SELECT * FROM invites WHERE created_by = ${user.id}`;
|
||||||
|
user.invites = invites;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error([
|
||||||
|
"An error occurred while fetching user data",
|
||||||
|
error as Error,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
code: 500,
|
||||||
|
error: "An error occurred while fetching user data",
|
||||||
|
},
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete user.password;
|
||||||
|
delete user.authorization_token;
|
||||||
|
if (!isSelf) delete user.email;
|
||||||
|
|
||||||
|
user.roles = user.roles ? user.roles[0].split(",") : [];
|
||||||
|
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
user: user,
|
||||||
|
},
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { handler, routeDef };
|
|
@ -3,6 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1> hello </h1>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
16
types/session.d.ts
vendored
16
types/session.d.ts
vendored
|
@ -37,3 +37,19 @@ type Invite = {
|
||||||
max_uses: number;
|
max_uses: number;
|
||||||
role: string;
|
role: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GetUser = {
|
||||||
|
id?: UUID;
|
||||||
|
authorization_token?: UUID;
|
||||||
|
username?: string;
|
||||||
|
email?: string;
|
||||||
|
email_verified?: boolean;
|
||||||
|
password?: string;
|
||||||
|
avatar?: boolean;
|
||||||
|
roles?: string[];
|
||||||
|
timezone?: string;
|
||||||
|
invited_by?: UUID;
|
||||||
|
created_at?: Date;
|
||||||
|
last_seen?: Date;
|
||||||
|
invites?: Invite[];
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue