auth: update server authentication

This commit is contained in:
2025-09-28 01:58:59 -04:00
parent fa05830996
commit b79d3ac2cf
5 changed files with 290 additions and 1 deletions

1
.gitignore vendored
View File

@@ -137,3 +137,4 @@ dist
# Vite logs files # Vite logs files
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
**/.helix

View File

@@ -0,0 +1,229 @@
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { describeRoute, resolver } from "hono-openapi";
import {
getUserCredentials,
getUserInformation,
} from "../services/userService";
import shaHash from "../helper/hashing";
import { PrismaClient } from "@prisma/client";
import {
loginSchema,
validateTokenSchema,
refreshTokenSchema,
logoutSchema,
authResponseSchema,
validationResponseSchema,
errorResponseSchema,
successResponseSchema,
} from "../validators/authValidator";
const prisma = new PrismaClient();
const authRoutes = new Hono();
// Login endpoint
authRoutes.post(
"/login",
describeRoute({
description: "User login",
responses: {
200: {
description: "Login successful",
content: {
"application/json": { schema: resolver(authResponseSchema) },
},
},
401: {
description: "Invalid credentials",
content: {
"application/json": { schema: resolver(errorResponseSchema) },
},
},
},
}),
zValidator("json", loginSchema),
async (c) => {
try {
const { username, password } = await c.req.json();
// Find user by username
const user = await prisma.user.findFirst({
where: { username: username },
});
if (!user) {
return c.json({ error: "Invalid username or password" }, 401);
}
// Get user credentials
const userCredentials = await getUserCredentials(user.id);
if (!userCredentials) {
return c.json({ error: "Invalid username or password" }, 401);
}
// Hash
// const hashedPassword = shaHash(password, user.id);
// Verify password
if (password !== userCredentials.password) {
return c.json({ error: "Invalid username or password" }, 401);
}
// Generate new token
const token = crypto.randomUUID();
// Update user's token in database
await prisma.userAuth.update({
where: { userId: user.id },
data: { token: token },
});
// Get full user information
const userInfo = await getUserInformation(user.id);
if (!userInfo) {
return c.json({ error: "Failed to get user information" }, 500);
}
return c.json({
user: userInfo,
token: token,
});
} catch (error) {
console.error("Login error:", error);
return c.json({ error: "Internal server error" }, 500);
}
},
);
// Token validation endpoint
authRoutes.post(
"/validate",
describeRoute({
description: "Validate user token",
responses: {
200: {
description: "Token validation result",
content: {
"application/json": { schema: resolver(validationResponseSchema) },
},
},
},
}),
zValidator("json", validateTokenSchema),
async (c) => {
try {
const { token, userId } = await c.req.json();
// Get user credentials
const userCredentials = await getUserCredentials(userId);
if (!userCredentials || userCredentials.token !== token) {
return c.json({ valid: false });
}
// Get user information
const userInfo = await getUserInformation(userId);
if (!userInfo) {
return c.json({ valid: false });
}
return c.json({
valid: true,
user: userInfo,
});
} catch (error) {
return c.json({ valid: false });
}
},
);
// Token refresh endpoint
authRoutes.post(
"/refresh",
describeRoute({
description: "Refresh user token",
responses: {
200: {
description: "Token refreshed successfully",
content: {
"application/json": { schema: resolver(authResponseSchema) },
},
},
401: {
description: "Invalid token",
content: {
"application/json": { schema: resolver(errorResponseSchema) },
},
},
},
}),
zValidator("json", refreshTokenSchema),
async (c) => {
try {
const { userId, oldToken } = await c.req.json();
// Verify old token
const userCredentials = await getUserCredentials(userId);
if (!userCredentials || userCredentials.token !== oldToken) {
return c.json({ error: "Invalid token" }, 401);
}
// Generate new token
const newToken = crypto.randomUUID();
// Update token in database
await prisma.userAuth.update({
where: { userId: userId },
data: { token: newToken },
});
// Get user information
const userInfo = await getUserInformation(userId);
if (!userInfo) {
return c.json({ error: "Failed to get user information" }, 500);
}
return c.json({
user: userInfo,
token: newToken,
});
} catch (error) {
console.error("Token refresh error:", error);
return c.json({ error: "Internal server error" }, 500);
}
},
);
// Logout endpoint (invalidate token)
authRoutes.post(
"/logout",
describeRoute({
description: "User logout",
responses: {
200: {
description: "Logout successful",
content: {
"application/json": { schema: resolver(successResponseSchema) },
},
},
},
}),
zValidator("json", logoutSchema),
async (c) => {
try {
const { userId } = await c.req.json();
// Clear token in database
await prisma.userAuth.update({
where: { userId: userId },
data: { token: null },
});
return c.json({ success: true });
} catch (error) {
console.error("Logout error:", error);
return c.json({ error: "Internal server error" }, 500);
}
},
);
export default authRoutes;

View File

@@ -5,6 +5,7 @@ import messageRoutes from "./messageRoutes";
import { channelRoutes } from "./channelRoutes"; import { channelRoutes } from "./channelRoutes";
import instanceRoutes from "./instanceRoutes"; import instanceRoutes from "./instanceRoutes";
import { categoryRoutes } from "./categoryRoutes"; import { categoryRoutes } from "./categoryRoutes";
import authRoutes from "./authRoutes";
const routes = new Hono(); const routes = new Hono();
@@ -13,5 +14,6 @@ routes.route("/message", messageRoutes);
routes.route("/channel", channelRoutes); routes.route("/channel", channelRoutes);
routes.route("/instance", instanceRoutes); routes.route("/instance", instanceRoutes);
routes.route("/category", categoryRoutes); routes.route("/category", categoryRoutes);
routes.route("/auth", authRoutes);
export default routes; export default routes;

View File

@@ -0,0 +1,57 @@
import { z } from "zod";
export const loginSchema = z.object({
username: z.string().min(3).max(30),
password: z.string().min(1),
});
export const validateTokenSchema = z.object({
token: z.string(),
userId: z.uuidv7(),
});
export const refreshTokenSchema = z.object({
userId: z.uuidv7(),
oldToken: z.string(),
});
export const logoutSchema = z.object({
userId: z.uuidv7(),
});
// Response schemas for OpenAPI documentation
export const authResponseSchema = z.object({
user: z.object({
id: z.string(),
userName: z.string(),
nickName: z.string().nullable(),
bio: z.string().nullable(),
picture: z.string().nullable(),
banner: z.string().nullable(),
admin: z.boolean(),
status: z.enum(["online", "offline", "dnd", "idle", "invis"]),
role: z.array(z.any()),
}),
token: z.string(),
});
export const validationResponseSchema = z.object({
valid: z.boolean(),
user: z.any().optional(),
});
export const errorResponseSchema = z.object({
error: z.string(),
});
export const successResponseSchema = z.object({
success: z.boolean(),
});
// Type exports
export type LoginInput = z.infer<typeof loginSchema>;
export type ValidateTokenInput = z.infer<typeof validateTokenSchema>;
export type RefreshTokenInput = z.infer<typeof refreshTokenSchema>;
export type LogoutInput = z.infer<typeof logoutSchema>;
export type AuthResponse = z.infer<typeof authResponseSchema>;
export type ValidationResponse = z.infer<typeof validationResponseSchema>;

View File

@@ -7,7 +7,7 @@ export const queryUserByIdSchema = z.object({
export const queryAllUsersByInstanceId = z.object({ export const queryAllUsersByInstanceId = z.object({
instanceId: z.uuidv7(), instanceId: z.uuidv7(),
}); });
import { is } from "zod/v4/locales";
export const createUserSchema = z.object({ export const createUserSchema = z.object({
username: z.string().min(3).max(30), username: z.string().min(3).max(30),
nickname: z.string().min(1).max(30).optional(), nickname: z.string().min(1).max(30).optional(),